in plugin-azure-server/src/main/resources/buildServerResources/images.vm.js [3:1532]
function ArmImagesViewModel($, ko, dialog, config) {
var self = this;
self.loadingSubscriptions = ko.observable(false);
self.loadingRegions = ko.observable(false);
self.loadingResources = ko.observable(false);
self.loadingOsType = ko.observable(false);
self.errorRegions = ko.observable("");
self.errorResources = ko.observable("");
// Credentials
var credentialsTypes = {
msi: 'msi',
service: 'service'
};
self.credentialsType = ko.observable();
var requiredForServiceCredentials = {
required: {
onlyIf: function () {
return self.credentialsType() === credentialsTypes.service
}
}
};
const azurePassStub = config.azurePassStub;
const displayPassIfNotEmpty = (val) => {
if (val) {
return azurePassStub;
}
return '';
};
const encryptData = val => window.BS.Encrypt.encryptData(val, config.publicKey);
self.credentials = ko.validatedObservable({
environment: ko.observable().extend({required: true}),
type: self.credentialsType,
tenantId: ko.observable('').trimmed().extend(requiredForServiceCredentials),
clientId: ko.observable('').trimmed().extend(requiredForServiceCredentials),
displayPassword: ko.observable(displayPassIfNotEmpty(config.clientSecret)),
clientSecret: ko.observable(config.clientSecret).trimmed().extend(requiredForServiceCredentials),
subscriptionId: ko.observable().extend({required: true}),
region: ko.observable()
});
self.credentials().displayPassword.subscribe(function (val) {
if (val !== azurePassStub) {
self.credentials().clientSecret(encryptData(val));
}
});
self.isValidClientData = ko.pureComputed(function () {
if (!self.credentials().type()) return false;
return self.credentials().type() === credentialsTypes.msi ||
self.credentials().tenantId.isValid() &&
self.credentials().clientId.isValid() &&
self.credentials().clientSecret.isValid();
});
self.isValidCredentials = ko.pureComputed(function () {
return self.credentials.isValid() && !self.errorRegions();
});
// Image details
var maxLength = 50;
// Note container can create a storage share with folders like <container-name>-plugins, etc. Max length of such name should be less than 63 characters
var containerVmPrefixMaxLength = 50;
var priceDivider = 100000;
var spotPriceDefault = 0.00001;
var deployTargets = {
specificGroup: 'SpecificGroup',
newGroup: 'NewGroup',
instance: 'Instance'
};
var imageTypes = {
container: 'Container',
image: 'Image',
template: 'Template',
vhd: 'Vhd',
galleryImage: 'GalleryImage',
};
var osTypes = {
linux: 'Linux',
windows: 'Windows'
};
self.deployTarget = ko.observable();
self.imageType = ko.observable();
self.osType = ko.observable();
self.spotVm = ko.observable(false);
self.enableSpotPrice = ko.observable(false);
self.template = ko.observable('');
self.disableTemplateModification = ko.observable(false);
self.userAssignedIdentity = ko.observable();
self.enableSystemAssignedIdentity = ko.observable(false);
var requiredForDeployment = {
required: {
onlyIf: function () {
return self.deployTarget() !== deployTargets.instance
}
}
};
self.templateParameters = ko.observableArray([]);
ko.pureComputed(function () {
return {
disableTemplateModification: self.disableTemplateModification(),
template: self.template(),
type: self.imageType()
}
})
.extend({
rateLimit: 200
})
.subscribe(value => {
let requiredParameters =
['vmName']
.map(name => Object.assign({}, {
name: name,
hasValue: true,
isProvided: true,
isMatched: false
}));
if (value.disableTemplateModification === true) {
['customData', 'teamcity-profile', 'teamcity-image-hash', 'teamcity-data-hash', 'teamcity-server', 'teamcity-source']
.map(name => Object.assign({}, {
name: name,
hasValue: true,
isProvided: true,
isMatched: false
}))
.forEach(p => requiredParameters.push(p));
}
let template;
try {
template = JSON.parse(value.template);
} catch (error) {
self.templateParameters(requiredParameters);
return;
}
if (!template || !template.parameters) {
self.templateParameters(requiredParameters);
return;
}
let parametersMap = {};
requiredParameters.forEach(p => parametersMap[p.name] = p);
Object
.entries(template.parameters)
.forEach(([key, value]) => {
let originalParam = parametersMap[key];
if (!!originalParam) {
originalParam.isMatched = true;
} else {
parametersMap[key] = Object.assign( {}, {
name: key,
hasValue: value.defaultValue !== undefined && value.defaultValue !== null,
isProvided: false,
isMatched: false
});
}
});
self.templateParameters(Object.values(parametersMap).sort((a, b) => a.name.localeCompare(b.name)));
});
self.image = ko.validatedObservable({
deployTarget: self.deployTarget.extend(requiredForDeployment),
region: ko.observable().extend(requiredForDeployment),
groupId: ko.observable().extend({
required: {
onlyIf: function () {
return self.deployTarget() === deployTargets.specificGroup
}
}
}),
imageType: self.imageType.extend(requiredForDeployment),
imageUrl: ko.observable('').trimmed().extend({rateLimit: 500}).extend({
required: {
onlyIf: function () {
return self.deployTarget() !== deployTargets.instance && self.imageType() === imageTypes.vhd;
}
}
}),
imageId: ko.observable().extend({
required: {
onlyIf: function () {
return self.deployTarget() !== deployTargets.instance &&
(self.imageType() === imageTypes.image || self.imageType() === imageTypes.galleryImage || self.imageType() === imageTypes.container);
}
}
}),
instanceId: ko.observable().extend({
required: {
onlyIf: function () {
return self.deployTarget() === deployTargets.instance;
}
}
}),
template: self.template.extend({
required: {
onlyIf: function () {
return self.deployTarget() !== deployTargets.instance &&
self.imageType() === imageTypes.template;
}
}
}).extend({
validation: {
validator: function (value) {
if (!value) return true;
var root;
try {
root = JSON.parse(value);
} catch (error) {
console.log("Unable to parse template: " + error);
return false;
}
if (!root) {
console.log("Invalid template object");
return false;
}
if (self.disableTemplateModification() === true) {
if (!root.parameters) {
console.log("No parameters defined");
return false;
}
if (!root.resources) {
console.log("No resources defined");
return false;
}
} else {
if (!root.parameters || !root.parameters.vmName) {
console.log("No 'vmName' parameter defined");
return false;
}
if (!root.resources || !ko.utils.arrayFirst(root.resources, function (resource) {
return resource.name === "[parameters('vmName')]";
})) {
console.log("No virtual machine resource with name set to vmName parameter");
return false;
}
}
return true;
},
message: "Invalid template value"
}
}),
templateParameters: self.templateParameters.extend({
required: {
onlyIf: function() {
return self.deployTarget() !== deployTargets.instance &&
self.imageType() === imageTypes.template;
}
}
}).extend({
validation: {
validator: function (value) {
if (self.imageType() !== imageTypes.template) return true;
let result = value.reduce((acc, current) => acc || !current.isMatched && (current.isProvided || !current.hasValue), false);
return !result;
},
message: "Invalid template parameters"
}
}),
networkId: ko.observable().extend({
required: {
onlyIf: function () {
return self.deployTarget() !== deployTargets.instance &&
self.imageType() !== imageTypes.template &&
self.imageType() !== imageTypes.container
}
}
}),
subnetId: ko.observable().extend({
required: {
onlyIf: function () {
return self.deployTarget() !== deployTargets.instance &&
self.imageType() !== imageTypes.template &&
self.imageType() !== imageTypes.container
}
}
}),
osType: self.osType.extend({
required: {
onlyIf: function () {
return self.imageType() !== imageTypes.template &&
self.deployTarget() !== deployTargets.instance
}
}
}),
maxInstances: ko.observable(1).extend({required: true, min: 0}),
vmSize: ko.observable().extend({
required: {
onlyIf: function () {
return self.deployTarget() !== deployTargets.instance &&
self.imageType() !== imageTypes.template &&
self.imageType() !== imageTypes.container
}
}
}),
numberCores: ko.observable(1).extend({min: 0}).extend({
required: {
onlyIf: function () {
return self.imageType() === imageTypes.container
}
}
}),
memory: ko.observable(1).extend({min: 0}).extend({
required: {
onlyIf: function () {
return self.imageType() === imageTypes.container
}
}
}),
registryUsername: ko.observable(),
registryPassword: ko.observable(),
storageAccount: ko.observable(),
storageAccountType: ko.observable(),
vmNamePrefix: ko.observable('').trimmed().extend({
required: {
onlyIf: function () {
return self.imageType() !== imageTypes.image ||
((self.imageType() === imageTypes.image || self.imageType() === imageTypes.galleryImage) && self.osType() != null)
}
}
}).extend({
validation: {
validator: function (value) {
return self.originalImage && self.originalImage.vmNamePrefix === value || !self.vmNamePrefixes.has(value);
},
message: 'Name prefix should be unique within subscription'
}
}).extend({
validation: {
validator: function (value) {
return ko.validation.rules['pattern'].validator(value, /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/i) && self.imageType() === imageTypes.container ||
self.imageType() !== imageTypes.container;
},
message: 'Name can contain alphanumeric characters and hyphen'
}
}).extend({
validation: {
validator: function (value) {
var namePattern = self.osType() === osTypes.linux ? /^[a-z0-9][\.-a-z0-9]*?$/i : /^[a-z0-9][-a-z0-9]*?$/i;
return ko.validation.rules['pattern'].validator(value, namePattern) && self.imageType() !== imageTypes.container ||
self.imageType() === imageTypes.container;
},
message: function(params, observable) {
return self.osType() === osTypes.linux
? 'Name can contain alphanumeric characters, hyphen and period'
: 'Name can contain alphanumeric characters and hyphen';
}
}
}).extend({
validation: {
validator: function (value) {
return !value || value.length <= maxLength ||
self.deployTarget() === deployTargets.instance ||
self.imageType() === imageTypes.container;
},
message: 'Please enter no more than ' + maxLength + ' characters.'
}
}).extend({
validation: {
validator: function (value) {
return !value || value.length <= containerVmPrefixMaxLength ||
self.imageType() != imageTypes.container;
},
message: 'Please enter no more than ' + containerVmPrefixMaxLength + ' characters.'
}
}),
vmPublicIp: ko.observable(false),
vmUsername: ko.observable('').trimmed().extend({
required: {
onlyIf: function () {
return self.deployTarget() !== deployTargets.instance &&
self.imageType() !== imageTypes.template &&
self.imageType() !== imageTypes.container
}
}
})
.extend({minLength: 3, maxLength: maxLength}),
vmPassword: ko.observable('').trimmed().extend({
required: {
onlyIf: function () {
return self.deployTarget() !== deployTargets.instance &&
self.imageType() !== imageTypes.template &&
self.imageType() !== imageTypes.container
}
}
})
.extend({minLength: 8}),
reuseVm: ko.observable(false),
agentPoolId: ko.observable().extend({required: true}),
profileId: ko.observable(),
customEnvironmentVariables: ko.observable('').extend({
pattern: {
message: 'Incorrect environment variables format',
params: /^(((([a-z_][a-z0-9_]*?)=.*?)|\s*))*$/i
}
}),
customTags: ko.observable('').extend({
pattern: {
message: 'Incorrect custom tags format',
params: /^(((([^<>%&\\\\?/]*?)=.*?)|\s*))*$/i
}
}),
spotVm: self.spotVm,
enableSpotPrice: self.enableSpotPrice,
spotPrice: ko.observable().extend({
required: {
onlyIf: function () {
return (self.imageType() === imageTypes.image || self.imageType() === imageTypes.galleryImage) && self.spotVm() && self.enableSpotPrice()
}
}
})
.extend({min: 0.00001, max: 20000}),
enableAcceleratedNetworking: ko.observable(false),
disableTemplateModification: self.disableTemplateModification,
userAssignedIdentity: ko.observable(),
enableSystemAssignedIdentity: self.enableSystemAssignedIdentity
});
// Data from Azure APIs
self.subscriptions = ko.observableArray([]);
self.resourceGroups = ko.observableArray([]);
self.regions = ko.observableArray([]);
self.regionName = ko.observable("");
self.sourceImages = ko.observableArray([]);
self.instances = ko.observableArray([]);
self.networks = ko.observableArray([]);
self.subNetworks = ko.observableArray([]);
self.vmSizes = ko.observableArray([]);
self.storageAccounts = ko.observableArray([]);
self.agentPools = ko.observableArray([]);
self.osTypes = ko.observableArray([osTypes.linux, osTypes.windows]);
self.osTypeImage = {
"Linux": "/img/os/lin-small-bw.png",
"Windows": "/img/os/win-small-bw.png"
};
self.environments = ko.observableArray([
{id: "AZURE", text: "Azure"},
{id: "AZURE_CHINA", text: "Azure China"},
{id: "AZURE_GERMANY", text: "Azure Germany"},
{id: "AZURE_US_GOVERNMENT", text: "Azure US Government"}
]);
self.storageAccountTypes = ko.observableArray([
{id: "Standard_LRS", text: "HDD"},
{id: "Premium_LRS", text: "SSD"}
]);
self.deployTargets = ko.observableArray([
{id: deployTargets.specificGroup, text: "Specific resource group"},
{id: deployTargets.newGroup, text: "New resource group"},
{id: deployTargets.instance, text: "Use existing virtual machine"}
]);
self.imageTypes = ko.observableArray([
{id: imageTypes.container, text: "Container"},
{id: imageTypes.image, text: "Image"},
{id: imageTypes.galleryImage, text: "Shared Image Gallery"},
{id: imageTypes.template, text: "Template"},
{id: imageTypes.vhd, text: "VHD"}
]);
self.filteredSourceImages = ko.pureComputed(function() {
return ko.utils.arrayFilter(self.sourceImages(), function(sourceImage) {
return sourceImage.isGalleryImage === (self.imageType() === imageTypes.galleryImage);
});
});
// Hidden fields for serialized values
self.images_data = ko.observable();
self.passwords_data = ko.observable(config.passwords_data);
// Deserialized values
self.images = ko.observableArray();
self.instances = ko.observableArray();
self.nets = {};
self.vmNamePrefixes = new Set();
self.credentialsType.subscribe(function () {
self.loadSubscriptions();
});
self.credentials().environment.subscribe(function () {
self.loadSubscriptions();
});
self.credentials().tenantId.subscribe(function () {
self.loadSubscriptions();
});
self.credentials().clientId.subscribe(function () {
self.loadSubscriptions();
});
self.credentials().clientSecret.subscribe(function () {
self.loadSubscriptions();
});
self.credentials().subscriptionId.subscribe(function (subscriptionId) {
if (!subscriptionId) return;
var subscription = ko.utils.arrayFirst(self.subscriptions(), function (item) {
return item.id === subscriptionId;
});
if (!subscription) {
self.subscriptions([{id: subscriptionId, text: subscriptionId}]);
}
self.loadRegions();
});
self.image().deployTarget.subscribe(function (deployTarget) {
if (deployTarget !== deployTargets.specificGroup) {
self.image().groupId(null);
}
if (deployTarget === deployTargets.instance) {
self.image().registryUsername("");
self.image().registryPassword("");
self.image().vmPassword("");
self.image().vmUsername("");
}
});
self.image().groupId.subscribe(function (groupId) {
if (!groupId) return;
var group = ko.utils.arrayFirst(self.resourceGroups(), function (item) {
return item.text === groupId;
});
if (group) {
self.image().region(group.region);
}
});
self.image().region.subscribe(function (value) {
if (!value) return;
var region = ko.utils.arrayFirst(self.regions(), function (item) {
return item.id === value;
});
if (region) {
self.regionName(region.text)
}
loadResourcesByRegion();
if (self.imageType() === imageTypes.vhd) {
loadOsType();
}
});
self.image().imageUrl.subscribe(function (url) {
if (!url) return;
loadOsType(url);
if (self.image().vmNamePrefix()) return;
// Fill vm name prefix from url
var fileName = self.getFileName(url);
var vmName = getVmNamePrefix(fileName);
self.image().vmNamePrefix(vmName);
});
self.image().imageId.subscribe(function (value) {
if (!value) return;
if (self.image().imageType() === imageTypes.container) {
if (!self.image().osType()) {
if (value.indexOf("nanoserver") > 0 || value.indexOf("windowsservercore") > 0) {
self.image().osType(osTypes.windows);
}
}
if (self.image().vmNamePrefix()) return;
var groupName = getGroupName(value);
self.image().vmNamePrefix(groupName);
} else {
var image = ko.utils.arrayFirst(self.sourceImages(), function (item) {
return item.id === value;
});
if (image) {
var osType = image.osType;
self.osType(osType);
self.image().osType(osType);
}
if (self.image().vmNamePrefix()) return;
// Fill vm prefix from image ID
var imageName = self.getFileName(value);
var vmName = getVmNamePrefix(imageName);
self.image().vmNamePrefix(vmName);
}
});
self.image().instanceId.subscribe(function (value) {
if (!value) return;
// Fill vm prefix from instance ID
var imageName = self.getFileName(value);
self.image().vmNamePrefix(imageName);
self.image().maxInstances(1)
});
self.image().vmSize.subscribe(function (value) {
if (!value) return;
var storageTypes = [{id: "Standard_LRS", text: "HDD"}];
if (/^(Basic|Standard)_.+s.*$/i.test(value)) {
storageTypes.push({id: "Premium_LRS", text: "SSD"});
}
self.storageAccountTypes(storageTypes);
});
self.image().networkId.subscribe(function (networkId) {
var subNetworks = self.nets[networkId] || [];
self.subNetworks(subNetworks);
});
ko.pureComputed(function() {
self.filteredSourceImages();
return self.imageType();
})
.extend({deferred : true, notify: 'always'})
.subscribe(function (imageType) {
if (imageType === imageTypes.image || imageType === imageTypes.galleryImage) {
try {
var jImageId = $j("#" + config.imageListControlId);
if (!jImageId.length) return;
if (jImageId.ufd("instance") === undefined) {
BS.enableJQueryDropDownFilter(jImageId, {addEmphasis: true});
} else {
BS.jQueryDropdown(jImageId, {addEmphasis: true});
}
} catch(e) {
BS.Log.warn(e);
}
}
});
self.images_data.subscribe(function (data) {
var images = ko.utils.parseJson(data || "[]");
var saveValue = false;
images.forEach(function (image) {
if (image["source-id"]) {
image.vmNamePrefix = image["source-id"];
self.vmNamePrefixes.add(image.vmNamePrefix);
} else {
saveValue = true;
}
if (image.agent_pool_id) {
image.agentPoolId = image.agent_pool_id;
} else {
saveValue = true;
}
image.reuseVm = JSON.parse(image.reuseVm || "false");
image.vmPublicIp = JSON.parse(image.vmPublicIp || "false");
image.deployTarget = image.deployTarget || deployTargets.newGroup;
if (image.deployTarget === deployTargets.newGroup) {
image.region = image.region || self.credentials().region();
}
image.imageType = image.imageType || imageTypes.vhd;
image.spotVm = JSON.parse(image.spotVm || "false");
image.enableSpotPrice = JSON.parse(image.enableSpotPrice || "false");
image.enableAcceleratedNetworking = JSON.parse(image.enableAcceleratedNetworking || "false");
image.disableTemplateModification = JSON.parse(image.disableTemplateModification || "false");
image.userAssignedIdentity = image.userAssignedIdentity;
image.enableSystemAssignedIdentity = JSON.parse(image.enableSystemAssignedIdentity || "false");
});
self.images(images);
if (saveValue) saveImages();
});
self.enableSpotPrice.subscribe(function(data) {
self.image().spotPrice(spotPriceDefault);
});
self.spotVm.subscribe(function(data) {
self.image().enableSpotPrice(false);
});
// Dialogs
self.originalImage = null;
self.showDialog = function (data) {
if (!self.isValidCredentials() || self.loadingSubscriptions() || self.loadingRegions()) {
return false;
}
self.originalImage = data;
var model = self.image();
var image = data || {
deployTarget: deployTargets.specificGroup,
groupId: self.getResourceGroup(),
imageType: imageTypes.container,
imageId: "jetbrains/teamcity-agent",
vmNamePrefix: "tc-agent",
osType: osTypes.linux,
maxInstances: 1,
numberCores: 2,
memory: 2,
customEnvironmentVariables: "",
customTags: "",
spotVm: false,
enableSpotPrice: false,
spotPrice: spotPriceDefault * priceDivider,
userAssignedIdentity: "",
systemAssignedIdentity: false,
};
// Pre-fill collections while loading resources
var imageId = image.imageId;
if (imageId
&& (image.imageType === imageTypes.image || image.imageType === imageTypes.galleryImage)
&& !ko.utils.arrayFirst(self.sourceImages(), function (item) {
return item.id === imageId;
})) {
self.sourceImages([{
id: imageId,
text: image.imageType === imageTypes.image ? self.getFileName(imageId) : self.getGalleryImageName(imageId),
osType: image.osType,
isGalleryImage: image.imageType === imageTypes.galleryImage
}]);
}
var instanceId = image.instanceId;
if (instanceId && !ko.utils.arrayFirst(self.instances(), function (item) {
return item.id === instanceId;
})) {
self.instances([{id: instanceId, text: self.getFileName(instanceId)}]);
}
var vmSize = image.vmSize;
if (vmSize && self.vmSizes.indexOf(vmSize) < 0) {
self.vmSizes([vmSize]);
}
var networkId = image.networkId;
if (networkId && self.networks.indexOf(networkId) < 0) {
self.networks([networkId]);
var subNetworks = [image.subnetId];
self.nets[networkId] = subNetworks;
self.subNetworks(subNetworks);
}
var storageAccount = image.storageAccount;
if (storageAccount && self.storageAccounts.indexOf(storageAccount) < 0) {
self.storageAccounts([storageAccount]);
}
model.deployTarget(image.deployTarget || deployTargets.newGroup);
model.region(image.region);
model.groupId(image.groupId);
model.imageType(image.imageType || imageTypes.vhd);
model.imageUrl(image.imageUrl);
model.imageId(imageId);
model.instanceId(instanceId);
model.osType(image.osType);
model.networkId(networkId);
model.subnetId(image.subnetId);
model.vmSize(vmSize);
model.numberCores(image.numberCores);
model.memory(image.memory);
model.registryUsername(image.registryUsername);
model.maxInstances(image.maxInstances);
model.vmNamePrefix(image.vmNamePrefix);
model.vmPublicIp(image.vmPublicIp);
model.vmUsername(image.vmUsername);
model.reuseVm(image.reuseVm);
model.storageAccount(storageAccount);
model.storageAccountType(image.storageAccountType);
model.template(image.template);
model.agentPoolId(image.agentPoolId);
model.profileId(image.profileId);
model.customEnvironmentVariables(image.customEnvironmentVariables);
model.customTags(image.customTags);
model.spotVm(image.spotVm);
model.enableSpotPrice(image.enableSpotPrice);
model.spotPrice(image.spotPrice != null ? image.spotPrice/priceDivider : undefined);
model.enableAcceleratedNetworking(image.enableAcceleratedNetworking);
model.disableTemplateModification(image.disableTemplateModification);
model.userAssignedIdentity(image.userAssignedIdentity);
model.enableSystemAssignedIdentity(image.enableSystemAssignedIdentity);
model.registryPassword("");
model.vmPassword("");
if (image.deployTarget !== deployTargets.instance) {
if (image.groupId !== null) {
if (image.imageType === imageTypes.container) {
model.registryPassword(azurePassStub);
} else {
model.vmPassword(azurePassStub);
}
}
}
self.image.errors.showAllMessages(false);
ko.validation.group(self.image().vmNamePrefix).showAllMessages();
dialog.showDialog(!self.originalImage);
return false;
};
self.closeDialog = function () {
dialog.close();
return false;
};
self.saveImage = function () {
var model = self.image();
var image = {
deployTarget: model.deployTarget(),
groupId: model.groupId(),
region: model.region(),
imageType: model.imageType(),
imageUrl: model.imageUrl(),
imageId: model.imageId(),
instanceId: model.instanceId(),
osType: model.osType(),
networkId: model.networkId(),
subnetId: model.subnetId(),
maxInstances: model.maxInstances(),
vmNamePrefix: model.vmNamePrefix(),
vmPublicIp: model.vmPublicIp(),
vmSize: model.vmSize(),
numberCores: model.numberCores(),
memory: model.memory(),
registryUsername: model.registryUsername(),
vmUsername: model.vmUsername(),
reuseVm: model.reuseVm(),
storageAccount: model.storageAccount(),
storageAccountType: model.storageAccountType(),
template: model.template(),
agentPoolId: model.agentPoolId(),
profileId: model.profileId(),
customEnvironmentVariables: model.customEnvironmentVariables(),
customTags: model.customTags(),
spotVm: model.spotVm(),
enableSpotPrice: model.enableSpotPrice(),
spotPrice: model.spotPrice() != null ? Math.trunc(parseFloat(model.spotPrice())*priceDivider) : undefined,
enableAcceleratedNetworking: model.enableAcceleratedNetworking(),
disableTemplateModification: model.disableTemplateModification(),
userAssignedIdentity: model.userAssignedIdentity(),
enableSystemAssignedIdentity: model.enableSystemAssignedIdentity(),
};
let pass;
if (image.imageType === imageTypes.container) {
pass = model.registryPassword();
} else {
pass = model.vmPassword();
}
const finishImageModification = () => {
var originalImage = self.originalImage;
if (originalImage) {
self.images.replace(originalImage, image);
self.vmNamePrefixes.delete(originalImage.vmNamePrefix);
} else {
self.images.push(image);
}
self.images_data(JSON.stringify(self.images()));
self.vmNamePrefixes.add(image.vmNamePrefix);
dialog.close();
};
if (pass !== azurePassStub) {
$.post(config.updateImageRequestPath, {
"prop:vmNamePrefix": self.image().vmNamePrefix,
"prop:encrypted:secure:password": encryptData(pass),
"prop:imageUpdateType": "upsert",
"prop:encrypted:secure:passwords_data": self.passwords_data()
}).then(function (response) {
const $response = $j(response);
const data = $response.find("passwords_data").text();
self.passwords_data(data);
const errors = getErrors($response);
if (errors) {
alert("Failed to update image data: " + errors);
return;
}
finishImageModification();
});
} else {
finishImageModification();
}
return false;
};
self.deleteImage = function (image) {
var imageName = "";
if (image.deployTarget !== deployTargets.instance) {
switch (image.imageType) {
case imageTypes.image:
imageName = 'source image ' + self.getFileName(image.imageId);
break;
case imageTypes.template:
imageName = 'template';
break;
case imageTypes.vhd:
imageName = 'VHD ' + image.imageUrl;
break;
case imageTypes.container:
imageName = 'container ' + image.imageId;
break;
}
} else {
imageName = 'virtual machine ' + self.getFileName(image.instanceId);
}
var message = "Do you really want to delete agent image based on " + imageName + "?";
var remove = confirm(message);
if (!remove) {
return false;
}
$.post(config.updateImageRequestPath, {
"prop:vmNamePrefix": image.vmNamePrefix,
"prop:imageUpdateType": "delete",
"prop:encrypted:secure:passwords_data": self.passwords_data()
}).then(function (response) {
const $response = $j(response);
const errors = getErrors($response);
if (errors) {
alert("Failed to update image data: " + errors);
return;
}
const data = $response.find("passwords_data").text();
self.passwords_data(data);
self.images.remove(image);
self.vmNamePrefixes.delete(image.vmNamePrefix);
saveImages();
});
return false;
};
self.getContextPath = function() {
return config.contextPath;
};
self.getOsImage = function (data) {
if (!data) return "";
var image;
if (ko.unwrap(data.imageType) === imageTypes.template) {
image = "/img/buildTypeTemplate.png";
} else if (ko.unwrap(data.deployTarget) === deployTargets.instance) {
return ko.computed(function() {
var instance = ko.utils.arrayFirst(self.instances(), function(instance) {
return instance.id == data.instanceId
});
return "url('" + self.getContextPath() +
(instance !== null && instance.osType && self.osTypeImage[instance.osType] || "/img/buildType.png") +
"')";
});
} else {
image = self.osTypeImage[ko.unwrap(data.osType)];
}
return ko.observable("url('" + self.getContextPath() + image + "')");
};
self.loadSubscriptions = function () {
if (!self.isValidClientData() || self.loadingSubscriptions()) return;
self.loadingSubscriptions(true);
var url = getBasePath() + "&resource=subscriptions";
$.post(url, getCredentials()).then(function (response) {
var $response = $j(response);
var errors = getErrors($response);
if (errors) {
self.credentials().subscriptionId.setError(errors);
return;
} else {
self.credentials().subscriptionId.clearError();
}
var subscriptions = getSubscriptions($response);
self.subscriptions(subscriptions);
}, function (error) {
self.credentials().subscriptionId.setError("Failed to load subscriptions: " + error.message);
console.log(error);
}).always(function () {
self.loadingSubscriptions(false);
});
};
self.loadRegions = function (types) {
types = types || ['regions', 'resourceGroups', 'instances'];
var url = types.reduce(function (prev, element) {
return prev + "&resource=" + element;
}, getBasePath());
self.loadingRegions(true);
$.post(url, getCredentials()).then(function (response) {
var $response = $j(response);
var errors = getErrors($response);
if (errors) {
self.errorRegions(errors);
return;
} else {
self.errorRegions("");
}
if (types.indexOf('regions') >= 0) {
var regions = getRegions($response);
self.regions(regions);
}
if (types.indexOf('resourceGroups') >= 0) {
var groups = getResourceGroups($response);
self.resourceGroups(groups);
}
if (types.indexOf('instances') >= 0) {
var instances = getInstances($response);
self.instances(instances);
}
}, function (error) {
self.errorRegions("Failed to data: " + error.message);
console.log(error);
}).always(function () {
self.loadingRegions(false);
});
};
self.getResourceGroup = function() {
var resourceGroups = self.resourceGroups();
if (resourceGroups.length !== 1) return null;
return resourceGroups[0].text;
};
self.getFileName = function (url) {
var slashIndex = url.lastIndexOf('/');
var dotIndex = url.lastIndexOf('.');
if (dotIndex < slashIndex) {
dotIndex = url.length;
}
if (slashIndex > 0 && dotIndex > 0) {
return url.substring(slashIndex + 1, dotIndex);
}
return "";
};
self.getGalleryImageName = function(imageId) {
if (!imageId) return "N/A";
var parts = imageId.split("/");
var getPart = function(partName) {
partName = partName.toUpperCase();
for(var index = 0; index < parts.length; index++) {
var part = parts[index];
if (partName === part.toUpperCase()) return (index + 1) < parts.length ? parts[index + 1] : null;
}
return null;
};
return getPart("galleries") + "/" + getPart("images") + "/" + (getPart("versions") || "latest");
};
self.setDefaultTemplate = function () {
if (self.image().disableTemplateModification() !== true) {
self.image().template('{\n' +
' "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",\n' +
' "contentVersion": "1.0.0.0",\n' +
' "parameters": {\n' +
' "vmName": {\n' +
' "type": "string",\n' +
' "metadata": {\n' +
' "description": "This is the Virtual Machine name."\n' +
' }\n' +
' }\n' +
' },\n' +
' "variables": {\n' +
' "location": "[resourceGroup().location]",\n' +
' "nicName": "[concat(parameters(\'vmName\'), \'-net\')]",\n' +
' "subnetRef": "..."\n' +
' },\n' +
' "resources": [\n' +
' {\n' +
' "apiVersion": "2016-09-01",\n' +
' "type": "Microsoft.Network/networkInterfaces",\n' +
' "name": "[variables(\'nicName\')]",\n' +
' "location": "[variables(\'location\')]",\n' +
' "properties": {\n' +
' "ipConfigurations": [\n' +
' {\n' +
' "name": "[concat(parameters(\'vmName\'), \'-config\')]",\n' +
' "properties": {\n' +
' "privateIPAllocationMethod": "Dynamic",\n' +
' "subnet": {\n' +
' "id": "[variables(\'subnetRef\')]"\n' +
' }\n' +
' }\n' +
' }\n' +
' ]\n' +
' }\n' +
' },\n' +
' {\n' +
' "apiVersion": "2016-04-30-preview",\n' +
' "type": "Microsoft.Compute/virtualMachines",\n' +
' "name": "[parameters(\'vmName\')]",\n' +
' "location": "[variables(\'location\')]",\n' +
' "dependsOn": [\n' +
' "[concat(\'Microsoft.Network/networkInterfaces/\', variables(\'nicName\'))]"\n' +
' ],\n' +
' "properties": {\n' +
' "hardwareProfile": {\n' +
' "vmSize": "Standard_A2"\n' +
' },\n' +
' "osProfile": {\n' +
' "computerName": "[parameters(\'vmName\')]",\n' +
' "adminUsername": "...",\n' +
' "adminPassword": "..."\n' +
' },\n' +
' "storageProfile": {\n' +
' "osDisk": {\n' +
' "name": "[concat(parameters(\'vmName\'), \'-os\')]",\n' +
' "osType": "...",\n' +
' "caching": "ReadWrite",\n' +
' "createOption": "FromImage"\n' +
' },\n' +
' "imageReference": {\n' +
' "id": "..."\n' +
' }\n' +
' },\n' +
' "networkProfile": {\n' +
' "networkInterfaces": [\n' +
' {\n' +
' "id": "[resourceId(\'Microsoft.Network/networkInterfaces\', variables(\'nicName\'))]"\n' +
' }\n' +
' ]\n' +
' }\n' +
' }\n' +
' }\n' +
' ]\n' +
'}\n');
} else {
self.image().template('{\n' +
' "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",\n' +
' "contentVersion": "1.0.0.0",\n' +
' "parameters": {\n' +
'\t"vmName": {\n' +
'\t\t"type": "string"\n' +
'\t},\t\n' +
'\t"customData" : { \n' +
'\t\t"type" : "string"\n' +
'\t},\t\n' +
'\t"teamcity-profile": {\n' +
'\t\t"type": "string"\n' +
'\t},\n' +
'\t"teamcity-image-hash": {\n' +
'\t\t"type": "string"\n' +
'\t},\n' +
'\t"teamcity-data-hash": {\n' +
'\t\t"type": "string"\n' +
'\t},\n' +
'\t"teamcity-server": {\n' +
'\t\t"type": "string"\n' +
'\t},\n' +
'\t"teamcity-source": {\n' +
'\t\t"type": "string"\n' +
'\t}\n' +
' },\n' +
' "variables": {\n' +
' "location": "[resourceGroup().location]",\n' +
' "nicName": "[concat(parameters(\'vmName\'), \'-net\')]",\n' +
' "subnetRef": "..."\n' +
' },\n' +
' "resources": [\n' +
' {\n' +
' "apiVersion": "2016-09-01",\n' +
' "type": "Microsoft.Network/networkInterfaces",\n' +
' "name": "[variables(\'nicName\')]",\n' +
' "location": "[variables(\'location\')]",\n' +
' "properties": {\n' +
' "ipConfigurations": [\n' +
' {\n' +
' "name": "[concat(parameters(\'vmName\'), \'-config\')]",\n' +
' "properties": {\n' +
' "privateIPAllocationMethod": "Dynamic",\n' +
' "subnet": {\n' +
' "id": "[variables(\'subnetRef\')]"\n' +
' }\n' +
' }\n' +
' }\n' +
' ]\n' +
' }\n' +
' },\n' +
' {\n' +
' "apiVersion": "2022-03-01",\n' +
' "type": "Microsoft.Compute/virtualMachines",\n' +
' "name": "[parameters(\'vmName\')]",\n' +
' "location": "[variables(\'location\')]",\n' +
' "dependsOn": [\n' +
' "[concat(\'Microsoft.Network/networkInterfaces/\', variables(\'nicName\'))]"\n' +
' ],\n' +
'\t "tags": {\n' +
' "teamcity-profile": "[parameters(\'teamcity-profile\')]",\n' +
' "teamcity-image-hash": "[parameters(\'teamcity-image-hash\')]",\n' +
' "teamcity-data-hash": "[parameters(\'teamcity-data-hash\')]",\n' +
' "teamcity-server": "[parameters(\'teamcity-server\')]",\n' +
' "teamcity-source": "[parameters(\'teamcity-source\')]"\n' +
' },\n' +
' "properties": {\n' +
' "hardwareProfile": {\n' +
' "vmSize": "Standard_A2"\n' +
' },\n' +
' "osProfile": {\n' +
' "computerName": "[parameters(\'vmName\')]",\n' +
' "adminUsername": "...",\n' +
' "adminPassword": "...",\n' +
' "customData": "[parameters(\'customData\')]"\n' +
' },\n' +
' "storageProfile": {\n' +
' "osDisk": {\n' +
' "name": "[concat(parameters(\'vmName\'), \'-os\')]",\n' +
' "osType": "Windows",\n' +
' "caching": "ReadWrite",\n' +
' "createOption": "FromImage"\n' +
' },\n' +
' "imageReference": {\n' +
' "id": "...",\n' +
' "exactVersion": "..."\n' +
' }\n' +
' },\n' +
' "networkProfile": {\n' +
' "networkInterfaces": [\n' +
' {\n' +
' "id": "[resourceId(\'Microsoft.Network/networkInterfaces\', variables(\'nicName\'))]"\n' +
' }\n' +
' ]\n' +
' }\n' +
' }\n' +
' }\n' +
' ]\n' +
'}');
}
};
self.getTemplatePartameterTooltipText = function(param) {
if (param.isProvided && !param.isMatched) {
return "Parameter is required but not declared in the template";
}
if (param.isProvided && param.isMatched) {
return "Parameter is required and is declared in the template";
}
if (!param.hasValue) {
return "Required parameter is declared in the template but is not provided by TeamCity";
}
return "Parameter has a default value";
};
function saveImages() {
var images = self.images();
images.forEach(function (image) {
image["source-id"] = image.vmNamePrefix;
delete image.vmNamePrefix;
image["agent_pool_id"] = image.agentPoolId;
delete image.agentPoolId;
});
self.images_data(JSON.stringify(images));
}
function getBasePath() {
return config.baseUrl +
"?prop%3Aenvironment=" + encodeURIComponent(self.credentials().environment());
}
function getCredentials() {
var credentials = self.credentials();
var type = credentials.type();
if (type === credentialsTypes.msi) {
return {
"prop:credentialsType": type,
"prop:subscriptionId": credentials.subscriptionId()
};
} else {
return {
"prop:credentialsType": type,
"prop:subscriptionId": credentials.subscriptionId(),
"prop:tenantId": credentials.tenantId(),
"prop:clientId": credentials.clientId(),
"prop:encrypted:secure:clientSecret": credentials.clientSecret()
};
}
}
function loadResourcesByRegion() {
var region = self.image().region();
if (!region) return;
var url = getBasePath() +
"&resource=vmSizes" +
"&resource=networks" +
"&resource=images" +
"&resource=storageAccounts" +
"®ion=" + region;
self.loadingResources(true);
$.post(url, getCredentials()).then(function (response) {
var $response = $j(response);
var errors = getErrors($response);
if (errors) {
self.errorResources(errors);
return;
} else {
self.errorResources("");
}
var images = getImages($response);
self.sourceImages(images);
var vmSizes = getVmSizes($response);
self.vmSizes(vmSizes);
var storageAccounts = getStorageAccounts($response);
self.storageAccounts(storageAccounts);
var networks = getNetworks($response);
self.networks(networks);
self.image().networkId.valueHasMutated();
}, function (error) {
self.errorResources("Failed to load data: " + error.message);
console.log(error);
}).always(function () {
self.loadingResources(false);
});
}
function loadOsType(newUrl) {
var imageUrl = newUrl || self.image().imageUrl();
if (!imageUrl) {
return;
}
var url = getBasePath() +
"&resource=osType" +
"&imageUrl=" + encodeURIComponent(imageUrl) +
"®ion=" + self.image().region();
self.loadingOsType(true);
$.post(url, getCredentials()).then(function (response) {
var $response = $j(response);
var errors = getErrors($response);
if (errors) {
self.image().imageUrl.setError(errors);
return;
} else {
self.image().imageUrl.clearError();
}
var osType = $response.find("osType").text();
self.osType(osType);
self.image().osType(osType);
}, function (error) {
self.errorResources("Failed to load data: " + error.message);
console.log(error);
}).always(function () {
self.loadingOsType(false);
});
}
function getErrors($response) {
var $errors = $response.find("errors:eq(0) error");
if ($errors.length) {
return $errors.text();
}
return "";
}
function getSubscriptions($response) {
return $response.find("subscriptions:eq(0) subscription").map(function () {
return {id: $(this).attr("id"), text: $(this).text()};
}).get();
}
function getResourceGroups($response) {
return $response.find("resourceGroups:eq(0) resourceGroup").map(function () {
return {region: $(this).attr("region"), text: $(this).text()};
}).get();
}
function getRegions($response) {
return $response.find("regions:eq(0) region").map(function () {
return {id: $(this).attr("id"), text: $(this).text()};
}).get();
}
function getInstances($response) {
return $response.find("instances:eq(0) instance").map(function () {
return {id: $(this).attr("id"), text: $(this).text(), osType: $(this).attr("osType")};
}).get();
}
function getVmSizes($response) {
return $response.find("vmSizes:eq(0) vmSize").map(function () {
return $(this).text();
}).get();
}
function getImages($response) {
return $response.find("images:eq(0) image").map(function () {
return {
id: $(this).attr("id"),
osType: $(this).attr("osType"),
isGalleryImage: JSON.parse($(this).attr("isGalleryImage")),
text: $(this).text()
};
}).get();
}
function getStorageAccounts($response) {
return $response.find("storageAccounts:eq(0) storageAccount").map(function () {
return $(this).text();
}).get();
}
function getNetworks($response) {
self.nets = {};
return $response.find("networks:eq(0) network").map(function () {
var id = $(this).attr("id");
self.nets[id] = $(this).find("subnet").map(function () {
return $(this).text();
}).get();
return id;
}).get();
}
function getVmNamePrefix(name) {
if (!name) return "";
var vhdSuffix = name.indexOf("-osDisk.");
if (vhdSuffix > 0) name = name.substring(0, vhdSuffix);
return cleanupVmName(cleanupVmName(name).slice(-maxLength));
}
function cleanupVmName(name) {
return name.replace(/^([^a-z])*|([^\w])*$/g, '');
}
function getGroupName(name) {
return name.toLowerCase()
.replace(/[^a-z0-9]/g, '-')
.replace(/-+/g, '-')
.replace(/(^-|-$)/g, '');
}
(function loadAgentPools() {
var url = config.baseUrl + "?resource=agentPools&projectId=" + encodeURIComponent(config.projectId);
$.post(url).then(function (response) {
var $response = $j(response);
var errors = getErrors($response);
if (errors) {
self.errorResources(errors);
return;
} else {
self.errorResources("");
}
var agentPools = $response.find("agentPools:eq(0) agentPool").map(function () {
return {
id: $(this).attr("id"),
text: $(this).text()
};
}).get();
self.agentPools(agentPools);
self.image().agentPoolId.valueHasMutated();
}, function (error) {
self.errorResources("Failed to load data: " + error.message);
console.log(error);
}).always(function () {
self.loadingOsType(false);
});
})();
}