Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ubuntu 24 to basic images (fix presaved license types tool) #603

Merged
merged 1 commit into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -777,961 +777,962 @@
.get(builtInImage).get(Constants.INSTALL_GIT));
}
if ((builtInImage.equals(Constants.UBUNTU_2004_LTS)
|| builtInImage.equals(Constants.UBUNTU_2204_LTS))
|| builtInImage.equals(Constants.UBUNTU_2204_LTS) || builtInImage.equals(Constants.UBUNTU_2404_LTS))
&& template.isInstallDocker()) {
stringBuilder.append(getSeparator(template.getOsType()));
stringBuilder.append(
AzureVMManagementServiceDelegate.PRE_INSTALLED_TOOLS_SCRIPT
.get(builtInImage).get(Constants.INSTALL_DOCKER)
.replace("${ADMIN}",
template.getVMCredentials().getUsername()));
}
return stringBuilder.toString();
} catch (Exception e) {
LOGGER.log(Level.WARNING,
"AzureVMTemplate: getBasicInitScript: Get pre-installed tools script {0} failed.",
e);
return stringBuilder.toString();
}
}

public static String getSeparator(String osType) {
if (osType.equals(Constants.OS_TYPE_WINDOWS)) {
return "\r\n";
} else {
return "\n";
}
}

@SuppressWarnings("unused") // called by stapler in jelly
public boolean isType(String type) {
if (type == null || imageReference == null) {
return false;
}

return imageReference.type.getName().equals(type);
}

public boolean isTopLevelType(String type) {
if (this.imageTopLevelType == null && type.equals(Constants.IMAGE_TOP_LEVEL_BASIC)) {
return true;
}
return type != null && type.equalsIgnoreCase(this.imageTopLevelType);
}

@SuppressWarnings("ConstantConditions") // fields are assigned by xstream
private Object readResolve() {
labelDataSet = Label.parse(labels);
templateProvisionStrategy = new ProvisionStrategy();

if (StringUtils.isBlank(storageAccountType)) {
storageAccountType = SkuName.STANDARD_LRS.toString();
}

if (nodeProperties == null) {
nodeProperties = new DescribableList<>(Saveable.NOOP);
}

if (StringUtils.isNotBlank(agentLaunchMethod)) {
if (agentLaunchMethod.equalsIgnoreCase(Constants.LAUNCH_METHOD_SSH)) {
AzureSSHLauncher azureSSHLauncher = new AzureSSHLauncher();

if (StringUtils.isNotBlank(sshConfig)) {
azureSSHLauncher.setSshConfig(sshConfig);
}
if (preInstallSsh) {
azureSSHLauncher.setPreInstallSsh(preInstallSsh);
}
launcher = azureSSHLauncher;

} else if (agentLaunchMethod.equalsIgnoreCase(Constants.LAUNCH_METHOD_JNLP)) {
launcher = new AzureInboundLauncher();
}
agentLaunchMethod = null;
}

if (StringUtils.isBlank(newStorageAccountName) && StringUtils.isBlank(existingStorageAccountName)
&& StringUtils.isNotBlank(storageAccountName)) {
newStorageAccountName = storageAccountName;
storageAccountNameReferenceType = "new";
}
storageAccountName = getStorageAccountName(
storageAccountNameReferenceType, newStorageAccountName, existingStorageAccountName);

if (StringUtils.isBlank(diskType)) {
diskType = Constants.DISK_UNMANAGED;
}

if (retentionStrategy == null) {
retentionStrategy = new AzureVMCloudRetensionStrategy(0);
}

if (imageReference == null) {
imageReference = new ImageReferenceTypeClass();
}

if (availabilityType == null) {
availabilityType = new NoAvailabilityRequired();
}

if (imageReference.type == null) {
imageReference.type = imageReference.determineType();
}

if (tags == null) {
this.tags = new ArrayList<>();
}

if (StringUtils.isBlank(imageTopLevelType)) {
if (imageReference != null && (StringUtils.isNotBlank(imageReference.getUri())
|| StringUtils.isNotBlank(imageReference.getId())
|| StringUtils.isNotBlank(imageReference.getOffer())
|| StringUtils.isNotBlank(imageReference.getSku())
|| StringUtils.isNotBlank(imageReference.getPublisher()))) {
imageTopLevelType = Constants.IMAGE_TOP_LEVEL_ADVANCED;
} else {
imageTopLevelType = Constants.IMAGE_TOP_LEVEL_BASIC;
}
builtInImage = Constants.WINDOWS_SERVER_2016;
}

return this;
}

public String getLabels() {
return labels;
}

public String getLocation() {
return location;
}

public String getVirtualMachineSize() {
return virtualMachineSize;
}

public String getStorageAccountType() {
return StringUtils.isBlank(storageAccountType) ? SkuName.STANDARD_LRS.toString() : storageAccountType;
}

public String getOsDiskStorageAccountType() {
if (StringUtils.isBlank(osDiskStorageAccountType)) {
return getStorageAccountType();
}
return osDiskStorageAccountType;
}

@DataBoundSetter
public void setOsDiskStorageAccountType(String osDiskStorageAccountType) {
this.osDiskStorageAccountType = osDiskStorageAccountType;
}

public String getStorageAccountName() {
return storageAccountName;
}

public static String getStorageAccountName(String type, String newName, String existingName) {
//type maybe null in this version, so we can guess according to whether newName is blank or not
if (StringUtils.isBlank(type) && StringUtils.isNotBlank(newName)
|| StringUtils.isNotBlank(type) && type.equalsIgnoreCase("new")) {
return newName;
}
return existingName;
}

public String getDiskType() {
return diskType;
}

public boolean isEphemeralOSDisk() {
return ephemeralOSDisk;
}

@DataBoundSetter
public void setEphemeralOSDisk(boolean ephemeralOSDisk) {
this.ephemeralOSDisk = ephemeralOSDisk;
}

public boolean isEncryptionAtHost() {
return encryptionAtHost;
}

@DataBoundSetter
public void setEncryptionAtHost(boolean encryptionAtHost) {
this.encryptionAtHost = encryptionAtHost;
}

@DataBoundSetter
public void setOsDiskSize(int osDiskSize) {
this.osDiskSize = osDiskSize;
}

public int getOsDiskSize() {
return osDiskSize;
}

public String getStorageAccountNameReferenceType() {
return storageAccountNameReferenceType;
}

public void setStorageAccountName(String storageAccountName) {
this.storageAccountName = storageAccountName;
}

public String getNewStorageAccountName() {
return newStorageAccountName;
}

public String getExistingStorageAccountName() {
return existingStorageAccountName;
}

public Node.Mode getUsageMode() {
return usageMode == null ? Node.Mode.NORMAL : usageMode;
}

public boolean isStorageAccountNameReferenceTypeEquals(String type) {
if (this.storageAccountNameReferenceType == null && type.equalsIgnoreCase("new")) {
return true;
}
return type != null && type.equalsIgnoreCase(this.storageAccountNameReferenceType);
}

@DataBoundSetter
public void setUsageMode(Node.Mode usageMode) {
this.usageMode = usageMode;
}

@DataBoundSetter
public void setShutdownOnIdle(boolean shutdownOnIdle) {
this.shutdownOnIdle = shutdownOnIdle;
}

public boolean isShutdownOnIdle() {
return shutdownOnIdle;
}

public AzureAvailabilityType getAvailabilityType() {
return availabilityType;
}

public ImageReferenceTypeClass getImageReference() {
return imageReference;
}

public String getImageTopLevelType() {
return imageTopLevelType;
}

public String getBuiltInImage() {
return builtInImage;
}

@DataBoundSetter
public void setInstallGit(boolean installGit) {
this.installGit = installGit;
}

@DataBoundSetter
public void setInstallMaven(boolean installMaven) {
this.installMaven = installMaven;
}

@DataBoundSetter
public void setInstallQemu(boolean installQemu) {
this.installQemu = installQemu;
}

@DataBoundSetter
public void setInstallDocker(boolean installDocker) {
this.installDocker = installDocker;
}

public boolean isInstallGit() {
return installGit;
}

public boolean isInstallMaven() {
return installMaven;
}

public boolean isInstallDocker() {
return installDocker;
}

public boolean isInstallQemu() {
return installQemu;
}

public String getOsType() {
return osType;
}

public String getInitScript() {
return initScript;
}

public String getTerminateScript() {
return terminateScript;
}

public String getCredentialsId() {
return credentialsId;
}

public StandardUsernameCredentials getVMCredentials() throws AzureCloudException {
return AzureUtil.getCredentials(credentialsId);
}

public String getVirtualNetworkName() {
return virtualNetworkName;
}

public void setVirtualNetworkName(String virtualNetworkName) {
this.virtualNetworkName = virtualNetworkName;
}

public String getVirtualNetworkResourceGroupName() {
return this.virtualNetworkResourceGroupName;
}

public String getSubnetName() {
return subnetName;
}

public void setSubnetName(String subnetName) {
this.subnetName = subnetName;
}

@DataBoundSetter
public void setUsePrivateIP(boolean usePrivateIP) {
this.usePrivateIP = usePrivateIP;
}

public boolean getUsePrivateIP() {
return usePrivateIP;
}

public String getNsgName() {
return nsgName;
}

public String getAgentWorkspace() {
return agentWorkspace;
}

public String getJvmOptions() {
return jvmOptions;
}

public AzureVMCloud retrieveAzureCloudReference() {
return azureCloud;
}

public void addAzureCloudReference(AzureVMCloud cloud) {
azureCloud = cloud;
if (StringUtils.isBlank(storageAccountName)) {
storageAccountName = AzureVMAgentTemplate.generateUniqueStorageAccountName(
azureCloud.getResourceGroupName(), templateName);
newStorageAccountName = storageAccountName;
//if storageAccountNameReferenceType equals existing, we help to choose new directly
storageAccountNameReferenceType = "new";
}
}

public String getTemplateName() {
return templateName;
}

public String getTemplateDesc() {
return templateDesc;
}

public int getNoOfParallelJobs() {
return noOfParallelJobs;
}

public ProvisionStrategy getTemplateProvisionStrategy() {
return templateProvisionStrategy;
}

public void setTemplateProvisionStrategy(ProvisionStrategy templateProvisionStrategy) {
this.templateProvisionStrategy = templateProvisionStrategy;
}

/**
* Returns true if this template is disabled and cannot be used, false
* otherwise.
*
* @return True/false
*/
public boolean isTemplateDisabled() {
return this.templateDisabled;
}

@DataBoundSetter
public void setTemplateDisabled(boolean templateDisabled) {
this.templateDisabled = templateDisabled;
}

/**
* Is the template set up and verified?
*
* @return True if the template is set up and verified, false otherwise.
*/
public boolean isTemplateVerified() {
return templateVerified;
}

/**
* Set the template verification status.
*
* @param isValid True for verified + valid, false otherwise.
*/
public void setTemplateVerified(boolean isValid) {
templateVerified = isValid;
}

public String getTemplateStatusDetails() {
return templateStatusDetails;
}

public void setTemplateStatusDetails(String templateStatusDetails) {
this.templateStatusDetails = templateStatusDetails;
}

public String getResourceGroupName() {
// Allow overriding?
return retrieveAzureCloudReference().getResourceGroupName();
}

public String getResourceGroupReferenceType() {
return retrieveAzureCloudReference().getResourceGroupReferenceType();
}

public int getRetentionTimeInMin() {
return retentionTimeInMin;
}

public boolean getExecuteInitScriptAsRoot() {
return executeInitScriptAsRoot;
}

public void setExecuteInitScriptAsRoot(boolean executeAsRoot) {
executeInitScriptAsRoot = executeAsRoot;
}

public boolean getDoNotUseMachineIfInitFails() {
return doNotUseMachineIfInitFails;
}

@DataBoundSetter
public void setEnableMSI(boolean enableMSI) {
this.enableMSI = enableMSI;
}

public boolean isEnableMSI() {
return enableMSI;
}

@DataBoundSetter
public void setEnableUAMI(boolean enableUAMI) {
this.enableUAMI = enableUAMI;
}

public boolean isEnableUAMI() {
return enableUAMI;
}

@DataBoundSetter
public void setUamiID(String uamiID) {
this.uamiID = uamiID;
}

public String getUamiID() {
return uamiID;
}

public AzureComputerLauncher getLauncher() {
return launcher;
}

@DataBoundSetter
public void setDoNotUseMachineIfInitFails(boolean doNotUseMachineIfInitFails) {
this.doNotUseMachineIfInitFails = doNotUseMachineIfInitFails;
}

public AdvancedImage getAdvancedImageInside() {
boolean isSSH = launcher instanceof AzureSSHLauncher;
boolean preInstallSshLocal = launcher != null
&& isSSH && ((AzureSSHLauncher) launcher).isPreInstallSsh();
String sshConfigLocal = launcher != null
&& isSSH ? ((AzureSSHLauncher) launcher).getSshConfig() : null;
return new AdvancedImageBuilder()
.withCustomImage(imageReference.uri)
.withCustomManagedImage(imageReference.id)
.withGalleryImage(
imageReference.galleryName,
imageReference.galleryImageDefinition,
imageReference.galleryImageVersion,
imageReference.galleryImageSpecialized,
imageReference.gallerySubscriptionId,
imageReference.galleryResourceGroup
)
.withReferenceImage(
imageReference.publisher,
imageReference.offer,
imageReference.sku,
imageReference.version
)
.withNumberOfExecutors(String.valueOf(getNoOfParallelJobs()))
.withOsType(getOsType())
.withLaunchMethod(isSSH ? Constants.LAUNCH_METHOD_SSH : Constants.LAUNCH_METHOD_JNLP)
.withPreInstallSsh(preInstallSshLocal)
.withSshConfig(sshConfigLocal)
.withInitScript(getInitScript())
.withVirtualNetworkName(getVirtualNetworkName())
.withVirtualNetworkResourceGroupName(getVirtualNetworkResourceGroupName())
.withSubnetName(getSubnetName())
.withUsePrivateIP(getUsePrivateIP())
.withNetworkSecurityGroupName(getNsgName())
.withJvmOptions(getJvmOptions())
.withDisableTemplate(isTemplateDisabled())
.withRunScriptAsRoot(getExecuteInitScriptAsRoot())
.withDoNotUseMachineIfInitFails(getDoNotUseMachineIfInitFails())
.withEnableMSI(isEnableMSI())
.withEnableUAMI(isEnableUAMI())
.withGetUamiID(getUamiID())
.build();
}

public BuiltInImage getBuiltInImageInside() {
return new BuiltInImageBuilder().withBuiltInImageName(getBuiltInImage())
.withInstallGit(isInstallGit())
.withInstallDocker(isInstallDocker())
.withInstallMaven(isInstallMaven())
.withInstallQemu(isInstallQemu())
.build();
}

@SuppressWarnings("unchecked")
public Descriptor<AzureVMAgentTemplate> getDescriptor() {
return Jenkins.get().getDescriptor(getClass());
}

public Set<LabelAtom> getLabelDataSet() {
return labelDataSet;
}

public RetentionStrategy getRetentionStrategy() {
return retentionStrategy;
}

public int getMaximumDeploymentSize() {
return maximumDeploymentSize;
}

@DataBoundSetter
public void setMaximumDeploymentSize(int maximumDeploymentSize) {
this.maximumDeploymentSize = maximumDeploymentSize;
}

public String getLicenseType() {
return licenseType;
}

@DataBoundSetter
public void setLicenseType(String licenseType) {
this.licenseType = licenseType;
}

/**
* Provision new agents using this template.
*
* @param listener Not used
* @param numberOfAgents Number of agents to provision
* @return New deployment info if the provisioning was successful.
* @throws Exception May throw if provisioning was not successful.
*/
public AzureVMDeploymentInfo provisionAgents(TaskListener listener, int numberOfAgents) throws Exception {
return getServiceDelegate().createDeployment(this, numberOfAgents);
}

private AzureVMManagementServiceDelegate getServiceDelegate() {
return retrieveAzureCloudReference().getServiceDelegate();
}

/**
* If provisioning failed, handle the status and queue the template for
* verification.
*
* @param message Failure message
* @param failureStep Stage that failure occurred
*/
public void handleTemplateProvisioningFailure(String message, FailureStage failureStep) {
// Set as failed, waiting for the next interval
templateProvisionStrategy.failure();
// Set the details so that it's easier to see what's going on from the configuration UI.
setTemplateStatusDetails(message);
}

/**
* Verify that this template is correct and can be allocated.
*
* @return Empty list if this template is valid, list of errors otherwise
* @throws Exception On Error
*/
public List<String> verifyTemplate() throws Exception {
return getServiceDelegate().verifyTemplate(
templateName,
labels,
location,
virtualMachineSize,
storageAccountName,
storageAccountType,
noOfParallelJobs + "",
imageTopLevelType,
imageReference,
builtInImage,
osType,
launcher,
initScript,
credentialsId,
virtualNetworkName,
virtualNetworkResourceGroupName,
subnetName,
(AzureVMCloudBaseRetentionStrategy) retentionStrategy,
jvmOptions,
getResourceGroupName(),
true,
usePrivateIP,
nsgName);
}

/**
* Deletes the template.
*/
@POST
public HttpResponse doDoDelete(@AncestorInPath AzureVMCloud azureVMCloud) throws IOException {
Jenkins j = Jenkins.get();
j.checkPermission(Jenkins.ADMINISTER);
if (azureVMCloud == null) {
throw new IllegalStateException("Cloud could not be found");
}
azureVMCloud.removeTemplate(this);
j.save();
// take the user back.
return new HttpRedirect("../../templates");
}

@POST
public HttpResponse doConfigSubmit(StaplerRequest req, @AncestorInPath AzureVMCloud azureVMCloud)
throws IOException, ServletException, Descriptor.FormException {
Jenkins j = Jenkins.get();
j.checkPermission(Jenkins.ADMINISTER);
if (azureVMCloud == null) {
throw new IllegalStateException("Cloud could not be found");
}
azureVMCloud.removeTemplate(this);
AzureVMAgentTemplate newTemplate = reconfigure(req, req.getSubmittedForm());
if (StringUtils.isBlank(newTemplate.getTemplateName())) {
throw new Descriptor.FormException("Template name is mandatory", "templateName");
}
boolean templateNameExists = azureVMCloud.templateNameExists(newTemplate.getTemplateName());
if (templateNameExists) {
throw new Descriptor.FormException("Agent template name must be unique", "templateName");
}

azureVMCloud.addTemplate(newTemplate);
j.save();
// take the user back.
return FormApply.success("../../templates");
}

private AzureVMAgentTemplate reconfigure(@NonNull final StaplerRequest req, JSONObject form)
throws Descriptor.FormException {
if (form == null) {
return null;
}
return getDescriptor().newInstance(req, form);
}

@Extension
public static final class DescriptorImpl extends Descriptor<AzureVMAgentTemplate> {

@Override
@NonNull
public String getDisplayName() {
return "";
}

public List<Descriptor<RetentionStrategy<?>>> getAzureVMRetentionStrategy() {
List<Descriptor<RetentionStrategy<?>>> list = new ArrayList<>();
list.add(AzureVMCloudRetensionStrategy.DESCRIPTOR);
list.add(AzureVMCloudPoolRetentionStrategy.DESCRIPTOR);
list.add(AzureVMCloudOnceRetentionStrategy.DESCRIPTOR);
return list;
}

private static final Set<String> RECOMMENDED_IMAGES = Set.of(
// recommended by Ubuntu image publisher
"Standard_D2s_v3", "Standard_D4s_v3", "Standard_E2s_v3",
// most used by Azure users
"Standard_D2as_v4", "Standard_B2s", "Standard_B2ms", "Standard_DS2_v2", "Standard_B4ms",
"Standard_DS3_v2"
);

@POST
public ListBoxModel doFillNsgNameItems(
@QueryParameter("cloudName") String cloudName) {
Jenkins.get().checkPermission(Jenkins.SYSTEM_READ);

ListBoxModel model = new ListBoxModel();
model.add("--- Select Network Security Group in current resource group ---", "");

AzureVMCloud cloud = getAzureCloud(cloudName);
if (cloud == null) {
return model;
}

String azureCredentialsId = cloud.getAzureCredentialsId();
if (StringUtils.isBlank(azureCredentialsId)) {
return model;
}

String resourceGroupReferenceType = cloud.getResourceGroupReferenceType();
String newResourceGroupName = cloud.getNewResourceGroupName();
String existingResourceGroupName = cloud.getExistingResourceGroupName();

try {
AzureResourceManager azureClient = AzureResourceManagerCache.get(azureCredentialsId);
if (azureClient == null) {
return model;
}

String resourceGroupName = AzureVMCloud.getResourceGroupName(
resourceGroupReferenceType, newResourceGroupName, existingResourceGroupName);
PagedIterable<NetworkSecurityGroup> nsgs =
azureClient.networkSecurityGroups()
.listByResourceGroup(resourceGroupName);
for (NetworkSecurityGroup nsg : nsgs) {
model.add(nsg.name());
}
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Cannot list availability set: ", e);
}
return model;
}

@POST
public AutoCompletionCandidates doAutoCompleteVirtualMachineSize(
@QueryParameter String cloudName,
@QueryParameter String location,
@QueryParameter String value) {
Jenkins.get().checkPermission(Jenkins.SYSTEM_READ);

String azureCredentialsId = getAzureCredentialsIdFromCloud(cloudName);

AutoCompletionCandidates model = new AutoCompletionCandidates();
if (StringUtils.isBlank(azureCredentialsId)) {
return model;
}
Set<String> vmSizes = AzureClientHolder.getDelegate(azureCredentialsId).getVMSizes(location);
// TODO switch to combobox when it allows customising the filtering
// so that this actually works
// see https://github.com/jenkinsci/design-library-plugin/issues/349
if (StringUtils.isBlank(value)) {
vmSizes.stream().filter(RECOMMENDED_IMAGES::contains).forEach(model::add);
} else {
for (String vmSize : vmSizes) {
if (vmSize.toLowerCase().contains(value.toLowerCase())) {
model.add(vmSize);
}
}
}
return model;
}

@POST
public ListBoxModel doFillCredentialsIdItems(@QueryParameter String credentialsId) {
StandardListBoxModel model = new StandardListBoxModel();
Jenkins context = Jenkins.get();
if (!context.hasPermission(Jenkins.ADMINISTER)) {
return model.includeCurrentValue(credentialsId);
}

return model
.includeAs(ACL.SYSTEM, context, SSHUserPrivateKey.class)
.includeAs(ACL.SYSTEM, context, StandardUsernamePasswordCredentials.class);
}

@POST
public ListBoxModel doFillOsTypeItems() throws IOException, ServletException {
ListBoxModel model = new ListBoxModel();
model.add(Constants.OS_TYPE_LINUX);
model.add(Constants.OS_TYPE_WINDOWS);
return model;
}

@POST
public ListBoxModel doFillLicenseTypeItems() {
ListBoxModel model = new ListBoxModel();
model.add(Constants.LICENSE_TYPE_NONE);
model.add(Constants.LICENSE_TYPE_WINDOWS_CLIENT);
model.add(Constants.LICENSE_TYPE_WINDOWS_SERVER);
model.add("RHEL_BASE");
model.add("RHEL_BYOS");
model.add("RHEL_EUS");
model.add("RHEL_SAPAPPS");
model.add("RHEL_SAPHA");
model.add("RHEL_BASESAPAPPS");
model.add("RHEL_BASESAPHA");
model.add("SLES");
model.add("SLES_BYOS");
model.add("SLES_SAP");
model.add("SLES_HPC");
return model;
}

public AzureAvailabilityType getDefaultAvailabilityType() {
return new NoAvailabilityRequired();
}

private String getAzureCredentialsIdFromCloud(String cloudName) {
AzureVMCloud cloud = getAzureCloud(cloudName);

if (cloud != null) {
return cloud.getAzureCredentialsId();
}

return null;
}

private AzureVMCloud getAzureCloud(String cloudName) {
Cloud cloud = Jenkins.get().getCloud(cloudName);

if (cloud instanceof AzureVMCloud) {
return (AzureVMCloud) cloud;
}

return null;
}

@POST
public ListBoxModel doFillLocationItems(
@QueryParameter("cloudName") String cloudName
) {
Jenkins.get().checkPermission(Jenkins.SYSTEM_READ);

String azureCredentialsId = getAzureCredentialsIdFromCloud(cloudName);

ListBoxModel model = new ListBoxModel();
if (StringUtils.isBlank(azureCredentialsId)) {
return model;
}

AzureBaseCredentials credential = AzureCredentialUtil.getCredential(null, azureCredentialsId);
if (credential != null) {
String envName = credential.getAzureEnvironmentName();
String managementEndpoint = credential.getManagementEndpoint();
AzureVMManagementServiceDelegate delegate = AzureClientHolder.getDelegate(azureCredentialsId);
Set<String> locations = delegate
.getVirtualMachineLocations(managementEndpoint != null ? managementEndpoint : envName);
if (locations != null) {
Set<String> sortedLocations = new TreeSet<>(locations);
for (String location : sortedLocations) {
model.add(location);
}
}
}

return model;
}

@SuppressWarnings("unused") // Used by jelly
@Restricted(DoNotUse.class) // Used by jelly
public AzureComputerLauncher getDefaultComputerLauncher() {
return new AzureSSHLauncher();
}

@POST
public ListBoxModel doFillStorageAccountTypeItems(@QueryParameter String virtualMachineSize) {
ListBoxModel model = new ListBoxModel();
model.add("--- Select Storage Account Type ---", "");

model.add(SkuName.STANDARD_LRS.toString());

/*As introduced in Azure Docs, the size contains 'S' supports premium storage*/
if (virtualMachineSize.matches(".*_[a-zA-Z]([0-9]+[aAMm]?[Ss]|[Ss][0-9]+).*")) {
model.add(SkuName.PREMIUM_LRS.toString());
}
return model;
}

@POST
public ListBoxModel doFillOsDiskStorageAccountTypeItems() {
ListBoxModel model = new ListBoxModel();
model.add("--- Select Storage Account Type ---", "");

model.add(DiskSkuTypes.STANDARD_LRS.toString());
model.add(DiskSkuTypes.STANDARD_SSD_LRS.toString());
model.add(DiskSkuTypes.PREMIUM_LRS.toString());
return model;
}

@POST
public ListBoxModel doFillUsageModeItems() throws IOException, ServletException {
ListBoxModel model = new ListBoxModel();
for (Node.Mode m : hudson.Functions.getNodeModes()) {
model.add(m.getDescription(), m.getName());
}
return model;
}

@POST
public ListBoxModel doFillExistingStorageAccountNameItems(
@QueryParameter("cloudName") String cloudName,
@QueryParameter String storageAccountType) {
Jenkins.get().checkPermission(Jenkins.SYSTEM_READ);

AzureVMCloud cloud = getAzureCloud(cloudName);
ListBoxModel model = new ListBoxModel();
if (cloud == null) {
return model;
}
String azureCredentialsId = cloud.getAzureCredentialsId();

if (StringUtils.isBlank(azureCredentialsId)) {
return model;
}
String resourceGroupReferenceType = cloud.getResourceGroupReferenceType();
String newResourceGroupName = cloud.getNewResourceGroupName();
String existingResourceGroupName = cloud.getExistingResourceGroupName();

try {
AzureResourceManager azureClient = AzureResourceManagerCache.get(azureCredentialsId);
if (azureClient == null) {
return model;
}

String resourceGroupName = AzureVMCloud.getResourceGroupName(
resourceGroupReferenceType, newResourceGroupName, existingResourceGroupName);
PagedIterable<StorageAccount> storageAccountList =
azureClient.storageAccounts().listByResourceGroup(resourceGroupName);
for (StorageAccount storageAccount : storageAccountList) {
if (storageAccount.skuType().name().toString().equalsIgnoreCase(storageAccountType)) {
model.add(storageAccount.name());
}
}
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Cannot list storage account: ", e);
}
return model;
}

@POST
public ListBoxModel doFillBuiltInImageItems() {
ListBoxModel model = new ListBoxModel();
model.add(Constants.UBUNTU_2404_LTS);

Check warning on line 1735 in src/main/java/com/microsoft/azure/vmagent/AzureVMAgentTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 780-1735 are not covered by tests
model.add(Constants.UBUNTU_2204_LTS);
model.add(Constants.UBUNTU_2004_LTS);
model.add(Constants.WINDOWS_SERVER_2022);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -536,1162 +536,1166 @@
String imageId = getImageId(properties);

// Gallery Image is a special case for custom image, reuse the logic of custom image by replacing the imageId here
if (referenceType == ImageReferenceType.GALLERY) {
if (!isBasic && referenceType == ImageReferenceType.GALLERY) {
GalleryImageVersion galleryImageVersion;
String galleryImageVersionStr = template.getImageReference().getGalleryImageVersion();
String galleryImageDefinition = template.getImageReference().getGalleryImageDefinition();
String gallerySubscriptionId = template.getImageReference().getGallerySubscriptionId();
String galleryResourceGroup = template.getImageReference().getGalleryResourceGroup();
String galleryName = template.getImageReference().getGalleryName();
if (StringUtils.isBlank(galleryImageVersionStr) || StringUtils.isBlank(galleryImageDefinition) ||
StringUtils.isBlank(galleryResourceGroup) || StringUtils.isBlank(galleryName)) {
throw AzureCloudException.create("One of gallery name, gallery image version, image definition and image resource group "
+ "is blank.");
}
AzureResourceManager client = AzureResourceManagerCache.get(azureCredentialsId, gallerySubscriptionId);
if (client == null) {
return null;
}
if (Constants.VERSION_LATEST.equals(galleryImageVersionStr)) {
galleryImageVersion = getGalleryImageLatestVersion(galleryResourceGroup,
galleryName, galleryImageDefinition, client);
} else {
galleryImageVersion = client.galleryImageVersions()
.getByGalleryImage(galleryResourceGroup, galleryName,
galleryImageDefinition, galleryImageVersionStr);
}
if (galleryImageVersion == null) {
throw AzureCloudException.create("Can not find the right version for the gallery image.");
}
imageId = galleryImageVersion.id();
LOGGER.log(Level.INFO, "Create VM with gallery image id {0}", new Object[]{imageId});
putVariableIfNotBlank(tmp, "imageId", imageId);

Map<String, String> imageTags = galleryImageVersion.tags();
if (imageTags != null) {
String planInfo = imageTags.get("PlanInfo");
String planProduct = imageTags.get("PlanProduct");
String planPublisher = imageTags.get("PlanPublisher");

if (StringUtils.isNotBlank(planInfo) && StringUtils.isNotBlank(planProduct)
&& StringUtils.isNotBlank(planPublisher)) {
for (JsonNode resource : resources) {
String type = resource.get("type").asText();
if (type.contains("virtualMachine")) {
ObjectNode planNode = MAPPER.createObjectNode();
planNode.put("name", planInfo);
planNode.put("publisher", planPublisher);
planNode.put("product", planProduct);
((ObjectNode) resource).replace("plan", planNode);
}
}
}
}
}

if (imageId != null) {
addTagToVm(tmp, "JenkinsImageId", imageId);
}



// If using the custom script extension (vs. SSH) to startup the powershell scripts,
// add variables for that and upload the init script to the storage account
if (useCustomScriptExtension) {
String rootUrl = fixEmpty(Jenkins.get().getRootUrl());
if (rootUrl == null) {
throw AzureCloudException.create("Jenkins URL must be set");
}
putVariable(tmp, "jenkinsServerURL", rootUrl);
// Calculate the client secrets. The secrets are based off the machine name,
ArrayNode clientSecretsNode = ((ObjectNode) tmp.get("variables")).putArray("clientSecrets");
for (int i = 0; i < numberOfAgents; i++) {
clientSecretsNode.add(
JnlpAgentReceiver.SLAVE_SECRET.mac(String.format("%s%d", vmBaseName, i)));
}
// Upload the startup script to blob storage
String scriptName = String.format("%s%s", deploymentName, "init.ps1");
String initScript;
if (preInstallSshInWindows) {
initScript = loadScript(PRE_INSTALL_SSH_FILENAME);
} else {
initScript = (String) properties.get("initScript");
}
scriptUri = uploadCustomScript(template, scriptName, initScript);
putVariable(tmp, "startupScriptURI", scriptUri);
putVariable(tmp, "startupScriptName", scriptName);


if (template.isUseEntraIdForStorageAccount()) {
String uamiClientId = azureClient.identities().getById(template.getUamiID()).clientId();

for (JsonNode resource : resources) {
String type = resource.get("type").asText();
if (type.contains("virtualMachine")) {
ArrayNode vmResources = (ArrayNode) resource.get("resources");
JsonNode scriptExtension = vmResources.get(0);
ObjectNode scriptExtensionProperties = (ObjectNode) scriptExtension.get("properties");
ObjectNode protectedSettings = (ObjectNode) scriptExtensionProperties.get("protectedSettings");
protectedSettings.remove("storageAccountName");
protectedSettings.remove("storageAccountKey");
ObjectNode clientId = MAPPER.createObjectNode();
clientId.put("clientId", uamiClientId);
protectedSettings.replace("managedIdentity", clientId);
}
}
ObjectNode parameters = (ObjectNode) tmp.get("parameters");
parameters.remove("storageAccountKey");
} else {
List<StorageAccountKey> storageKeys = azureClient.storageAccounts()
.getByResourceGroup(template.getResourceGroupName(), storageAccountName)
.getKeys();
if (storageKeys.isEmpty()) {
throw AzureCloudException.create("Exception occurred while fetching the storage account key");
}
String storageAccountKey = storageKeys.get(0).value();

final ObjectNode storageAccountKeyNode = MAPPER.createObjectNode();
storageAccountKeyNode.put("type", "secureString");
storageAccountKeyNode.put("defaultValue", storageAccountKey);

// Add the storage account key
((ObjectNode) tmp.get("parameters")).replace("storageAccountKey", storageAccountKeyNode);
}
}

putVariable(tmp, "vmSize", template.getVirtualMachineSize());
// Grab the username/pass
StandardUsernameCredentials creds = template.getVMCredentials();

putVariableIfNotBlank(tmp, "storageAccountName", storageAccountName);
putVariableIfNotBlank(tmp, "storageAccountType", storageAccountType);
putVariableIfNotBlank(tmp, "blobEndpointSuffix", blobEndpointSuffix);

// Network properties. If the vnet name isn't blank then
// then subnet name can't be either (based on verification rules)
if (!isBasic && StringUtils.isNotBlank((String) properties.get("virtualNetworkName"))) {
copyVariableIfNotBlank(tmp, properties, "virtualNetworkName");
copyVariable(tmp, properties, "subnetName");
if (StringUtils.isNotBlank((String) properties.get("virtualNetworkResourceGroupName"))) {
copyVariable(tmp, properties, "virtualNetworkResourceGroupName");
} else {
putVariable(tmp, "virtualNetworkResourceGroupName", resourceGroupName);
}
} else {
addDefaultVNetResourceNode(tmp, resourceGroupName, tags);
}

if (template.isSpotInstance()) {
addSpotInstance(tmp);
}

if (template.isTrustedLaunch()) {
addTrustedLaunch(tmp);
}

if (!(Boolean) properties.get("usePrivateIP")) {
List<String> availabilityZones = new ArrayList<>();
if (availabilityType instanceof VirtualMachineScaleSet) {
var name = ((VirtualMachineScaleSet) availabilityType).getName();

availabilityZones = azureClient.virtualMachineScaleSets()
.getByResourceGroup(resourceGroupName, name)
.availabilityZones()
.stream()
.map(ExpandableStringEnum::toString)
.collect(Collectors.toList());
}

addPublicIPResourceNode(tmp, tags, availabilityZones);
}

if (template.isAcceleratedNetworking()) {
addAcceleratedNetworking(tmp);
}

if (StringUtils.isNotBlank((String) properties.get("nsgName"))) {
addNSGNode(tmp, (String) properties.get("nsgName"));
}

final ObjectNode parameters = MAPPER.createObjectNode();

defineParameter(tmp, "adminUsername", "string");
putParameter(parameters, "adminUsername", creds.getUsername());

defineParameter(tmp, "authenticationType", "string");
defineParameter(tmp, "adminPasswordOrKey", "secureString");
if (creds instanceof StandardUsernamePasswordCredentials) {
StandardUsernamePasswordCredentials passwordCredentials = (StandardUsernamePasswordCredentials) creds;
putParameter(parameters, "adminPasswordOrKey", passwordCredentials.getPassword().getPlainText());
putParameter(parameters, "authenticationType", "password");
} else {
SSHUserPrivateKey sshCredentials = (SSHUserPrivateKey) creds;
String privateKey = sshCredentials.getPrivateKeys().get(0);
String rsaPublicKey = KeyDecoder.getPublicKey(privateKey, Secret.toString(sshCredentials.getPassphrase()));
putParameter(parameters, "adminPasswordOrKey", rsaPublicKey);
putParameter(parameters, "authenticationType", "key");
}

if (!Constants.LICENSE_TYPE_NONE.equals(template.getLicenseType())) {
if (!Constants.NO_LICENSE_TYPE.contains(template.getLicenseType())) {
addLicenseType(tmp, template.getLicenseType());
}

// Register the deployment for cleanup
deploymentRegistrar.registerDeployment(
cloudName, template.getResourceGroupName(), deploymentName, scriptUri,
template.isUseEntraIdForStorageAccount());
// Create the deployment

String templateJson = tmp.toString();
LOGGER.log(Level.FINE, templateJson);
azureClient.deployments().define(deploymentName)
.withExistingResourceGroup(template.getResourceGroupName())
.withTemplate(templateJson)
.withParameters(parameters.toString())
.withMode(DeploymentMode.INCREMENTAL)
.beginCreate();
return new AzureVMDeploymentInfo(deploymentName, vmBaseName, numberOfAgents);
} catch (RuntimeException | InvalidPassphraseException | SshException e) {
LOGGER.log(Level.SEVERE,
String.format("Unable to deploy %d %s",
numberOfAgents, template.getTemplateName()),
e);
// Pass the info off to the template so that it can be queued for update.
template.handleTemplateProvisioningFailure(e.getMessage(), FailureStage.PROVISIONING);
try {
removeStorageBlob(
new URI(scriptUri),
template.getResourceGroupName(),
azureCredentialsId,
template.isUseEntraIdForStorageAccount()
);
} catch (Exception ex) {
LOGGER.log(Level.WARNING, "Delete initScript failed: {0}", scriptUri);
}
throw AzureCloudException.create(e);
} finally {
if (embeddedTemplate != null) {
embeddedTemplate.close();
}
}
}

private String getImageId(Map<String, Object> properties) {
Object imageId = properties.get("imageId");
Object imagePublisherObj = properties.get("imagePublisher");
if (imageId == null && imagePublisherObj == null) {
return null;
}

if (imagePublisherObj != null) {
Object imageOfferObj = properties.get("imageOffer");
Object imageSkuObj = properties.get("imageSku");
Object imageVersionObj = properties.get("imageVersion");

imageId = imagePublisherObj + "::" + imageOfferObj + "::" + imageSkuObj + "::" + imageVersionObj;
}
return (String) imageId;
}

private void addTagToVm(JsonNode template, String key, String value) {
ArrayNode resources = (ArrayNode) template.get("resources");
for (JsonNode resource : resources) {
String type = resource.get("type").asText();
if (type.contains("virtualMachine")) {
ObjectNode tags = (ObjectNode) resource.get("tags");
tags.put(key, StringUtils.left(value, 255));
return;
}
}
}

private void addSpotInstance(JsonNode template) {
ArrayNode resources = (ArrayNode) template.get("resources");
for (JsonNode resource : resources) {
String type = resource.get("type").asText();
if (type.contains("virtualMachine")) {
ObjectNode properties = (ObjectNode) resource.get("properties");
properties.put("priority", "Spot");
properties.put("evictionPolicy", "Delete");
}
}
}


private void addTrustedLaunch(JsonNode template) {
ObjectNode parameterNode = (ObjectNode) template.get("parameters");
ObjectNode securityTypeNode = MAPPER.createObjectNode();
securityTypeNode.put("type", "string");
securityTypeNode.put("defaultValue", "TrustedLaunch");
ArrayNode allowedValuesNode = MAPPER.createArrayNode();
allowedValuesNode.add("Standard");
allowedValuesNode.add("TrustedLaunch");
securityTypeNode.set("allowedValues", allowedValuesNode);
ObjectNode metaDataNode = MAPPER.createObjectNode();
metaDataNode.put("description", "Security Type of the Virtual Machine.");
securityTypeNode.set("metadata", metaDataNode);
parameterNode.set("securityType", securityTypeNode);

ObjectNode variableNode = (ObjectNode) template.get("variables");
ObjectNode profileNode = MAPPER.createObjectNode();
ObjectNode settingsNode = MAPPER.createObjectNode();
settingsNode.put("secureBootEnabled", true);
settingsNode.put("vTpmEnabled", true);

profileNode.set("uefiSettings", settingsNode);
profileNode.put("securityType", "[parameters('securityType')]");

variableNode.set("securityProfileJson", profileNode);



ArrayNode resources = (ArrayNode) template.get("resources");
for (JsonNode resource : resources) {
String type = resource.get("type").asText();
if (type.contains("virtualMachine")) {
ObjectNode properties = (ObjectNode) resource.get("properties");
properties.put("securityProfile", "[if(equals(parameters('securityType'), 'TrustedLaunch'), variables('securityProfileJson'), null())]");
}
}
}

private void addAcceleratedNetworking(JsonNode template) {
ArrayNode resources = (ArrayNode) template.get("resources");
for (JsonNode resource : resources) {
String type = resource.get("type").asText();
if (type.contains("networkInterfaces")) {
ObjectNode properties = (ObjectNode) resource.get("properties");
properties.put("enableAcceleratedNetworking", "true");
}
}
}

private void addLicenseType(JsonNode template, String licenseType) {
ArrayNode resources = (ArrayNode) template.get("resources");
for (JsonNode resource : resources) {
String type = resource.get("type").asText();
if (type.contains("virtualMachine")) {
ObjectNode properties = (ObjectNode) resource.get("properties");
properties.put("licenseType", licenseType);
return;
}
}
}

private void injectCustomTag(JsonNode resource, List<AzureTagPair> tags) {
ObjectNode tagsNode = (ObjectNode) resource.get("tags");
if (tags != null) {
for (AzureTagPair tag : tags) {
tagsNode.put(tag.getName(), tag.getValue());
}
}
}

private boolean checkImageParameter(AzureVMAgentTemplate template) {
if (StringUtils.isBlank(template.getImageReference().getPublisher())
|| StringUtils.isBlank(template.getImageReference().getOffer())
|| StringUtils.isBlank(template.getImageReference().getSku())) {
LOGGER.log(Level.SEVERE, "Missing Image Reference information when trying to add purchase plan to ARM template");
return false;
}
return true;
}

private GalleryImageVersion getGalleryImageLatestVersion(String galleryResourceGroup, String galleryName,
String galleryImageDefinition, AzureResourceManager client) throws AzureCloudException {

PagedIterable<GalleryImageVersion> galleryImageVersions = client.galleryImageVersions().listByGalleryImage(galleryResourceGroup, galleryName, galleryImageDefinition);
GalleryImageVersion latestVersion = null;
for (GalleryImageVersion galleryImageVersion : galleryImageVersions) {
if (latestVersion == null) {
latestVersion = galleryImageVersion;
continue;
}

OffsetDateTime currentPublishedDate = latestVersion.publishingProfile().publishedDate();
if (galleryImageVersion.publishingProfile().publishedDate().compareTo(currentPublishedDate) > 0) {
latestVersion = galleryImageVersion;
}
}
return latestVersion;
}

private static void putVariable(JsonNode template, String name, String value) {
((ObjectNode) template.get("variables")).put(name, value);
}

private static void putParameter(ObjectNode template, String name, String value) {
ObjectNode objectNode = MAPPER.createObjectNode();
objectNode.put("value", value);

template.set(name, objectNode);
}

private static void defineParameter(JsonNode template, String name, String value) {
ObjectNode objectNode = MAPPER.createObjectNode();
objectNode.put("type", value);

((ObjectNode) template.get("parameters")).set(name, objectNode);
}


private static void putVariableIfNotBlank(JsonNode template, String name, String value) {
if (StringUtils.isNotBlank(value)) {
putVariable(template, name, value);
}
}

private static void copyVariable(JsonNode template, Map<String, Object> properties, String name) {
putVariable(template, name, (String) properties.get(name));
}

private static void copyVariableIfNotBlank(JsonNode template, Map<String, Object> properties, String name) {
putVariableIfNotBlank(template, name, (String) properties.get(name));
}

private void addPublicIPResourceNode(
JsonNode template,
List<AzureTagPair> tags,
List<String> availabilityZones) throws IOException {

final String ipName = "variables('vmName'), copyIndex(), 'IPName'";
try (InputStream fragmentStream =
AzureVMManagementServiceDelegate.class.getResourceAsStream(PUBLIC_IP_FRAGMENT_FILENAME)) {

final ObjectNode publicIPFragment = (ObjectNode) MAPPER.readTree(fragmentStream);
if (!availabilityZones.isEmpty()) {
ArrayNode zones = MAPPER.createArrayNode();
for (String zone : availabilityZones) {
zones.add(zone);
}
publicIPFragment.set("zones", zones);
}

injectCustomTag(publicIPFragment, tags);
// Add the virtual network fragment
((ArrayNode) template.get("resources")).add(publicIPFragment);

// Because we created/updated this in the template, we need to add the appropriate
// dependsOn node to the networkInterface and the ipConfigurations properties
// "[concat('Microsoft.Network/publicIPAddresses/', variables('vmName'), copyIndex(), 'IPName')]"
// Find the network interfaces node
ArrayNode resourcesNodes = (ArrayNode) template.get("resources");
Iterator<JsonNode> resourcesNodesIter = resourcesNodes.elements();
while (resourcesNodesIter.hasNext()) {
JsonNode resourcesNode = resourcesNodesIter.next();
JsonNode typeNode = resourcesNode.get("type");
if (typeNode == null || !typeNode.asText().equals("Microsoft.Network/networkInterfaces")) {
continue;
}
// Find the dependsOn node
ArrayNode dependsOnNode = (ArrayNode) resourcesNode.get("dependsOn");
// Add to the depends on node.
dependsOnNode.add("[concat('Microsoft.Network/publicIPAddresses/'," + ipName + ")]");

//Find the ipConfigurations/ipconfig1 node
ArrayNode ipConfigurationsNode =
(ArrayNode) resourcesNode.get("properties").get("ipConfigurations");
Iterator<JsonNode> ipConfigNodeIter = ipConfigurationsNode.elements();
while (ipConfigNodeIter.hasNext()) {
JsonNode ipConfigNode = ipConfigNodeIter.next();
JsonNode nameNode = ipConfigNode.get("name");
if (nameNode == null || !nameNode.asText().equals("ipconfig1")) {
continue;
}
//find the properties node
ObjectNode propertiesNode = (ObjectNode) ipConfigNode.get("properties");
//add the publicIPAddress node
ObjectNode publicIPIdNode = MAPPER.createObjectNode();
publicIPIdNode.put("id", "[resourceId('Microsoft.Network/publicIPAddresses', concat("
+ ipName
+ "))]");
ObjectNode ipAddressPropertiesNode = MAPPER.createObjectNode();
ipAddressPropertiesNode.put("deleteOption", "Delete");

publicIPIdNode.set("properties", ipAddressPropertiesNode);
propertiesNode.set("publicIPAddress", publicIPIdNode);
break;
}
break;
}
}
}

private static void addNSGNode(
JsonNode template,
String nsgName) {

((ObjectNode) template.get("variables")).put("nsgName", nsgName);

ArrayNode resourcesNodes = (ArrayNode) template.get("resources");
Iterator<JsonNode> resourcesNodesIter = resourcesNodes.elements();
while (resourcesNodesIter.hasNext()) {
JsonNode resourcesNode = resourcesNodesIter.next();
JsonNode typeNode = resourcesNode.get("type");
if (typeNode == null || !typeNode.asText().equals("Microsoft.Network/networkInterfaces")) {
continue;
}

ObjectNode nsgNode = MAPPER.createObjectNode();
nsgNode.put(
"id",
"[resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))]"
);

// Find the properties node
// We will attach the provided NSG and not check if it's valid because that's done in the verification step
ObjectNode propertiesNode = (ObjectNode) resourcesNode.get("properties");
propertiesNode.set("networkSecurityGroup", nsgNode);
break;
}
}


private void addDefaultVNetResourceNode(
JsonNode template,
String resourceGroupName,
List<AzureTagPair> tags) throws IOException {
InputStream fragmentStream = null;
try {
// Add the definition of the vnet and subnet into the template
final String virtualNetworkName = Constants.DEFAULT_VNET_NAME;
final String subnetName = Constants.DEFAULT_SUBNET_NAME;
((ObjectNode) template.get("variables")).put("virtualNetworkName", virtualNetworkName);
((ObjectNode) template.get("variables")).put(
"virtualNetworkResourceGroupName", resourceGroupName);
((ObjectNode) template.get("variables")).put("subnetName", subnetName);

// Read the vnet fragment
fragmentStream = AzureVMManagementServiceDelegate.class.getResourceAsStream(
VIRTUAL_NETWORK_TEMPLATE_FRAGMENT_FILENAME);

final JsonNode virtualNetworkFragment = MAPPER.readTree(fragmentStream);
injectCustomTag(virtualNetworkFragment, tags);
// Add the virtual network fragment
((ArrayNode) template.get("resources")).add(virtualNetworkFragment);

// Because we created/updated this in the template, we need to add the appropriate
// dependsOn node to the networkInterface
// Microsoft.Network/virtualNetworks/<vnet name>
// Find the network interfaces node
ArrayNode resourcesNodes = (ArrayNode) template.get("resources");
Iterator<JsonNode> resourcesNodesIter = resourcesNodes.elements();
while (resourcesNodesIter.hasNext()) {
JsonNode resourcesNode = resourcesNodesIter.next();
JsonNode typeNode = resourcesNode.get("type");
if (typeNode == null || !typeNode.asText().equals("Microsoft.Network/networkInterfaces")) {
continue;
}
// Find the dependsOn node
ArrayNode dependsOnNode = (ArrayNode) resourcesNode.get("dependsOn");
// Add to the depends on node.
dependsOnNode.add("[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]");
break;
}
} finally {
if (fragmentStream != null) {
fragmentStream.close();
}
}
}


/**
* Uploads the custom script for a template to blob storage.
*
* @param template Template containing script to upload
* @param targetScriptName Script to upload
* @param initScript Specify initScript
* @return URI of script
* @throws AzureCloudException when uploading to blob storage fails
*/
public String uploadCustomScript(
AzureVMAgentTemplate template,
String targetScriptName,
String initScript) throws AzureCloudException {
String localInitScript = initScript != null ? initScript : "";
String targetStorageAccount = template.getStorageAccountName();
String targetStorageAccountType = template.getStorageAccountType();
String resourceGroupName = template.getResourceGroupName();
String resourceGroupReferenceType = template.getResourceGroupReferenceType();
String location = template.getLocation();
List<AzureTagPair> tags = concat(template.retrieveAzureCloudReference().getCloudTags(), template.getTags());

//make sure the resource group and storage account exist
try {
if (Constants.RESOURCE_GROUP_REFERENCE_TYPE_NEW.equals(resourceGroupReferenceType)) {
AzureVMCloud azureVMCloud = template.retrieveAzureCloudReference();
createAzureResourceGroup(azureClient, location, resourceGroupName, azureVMCloud.getCloudName());
}

createStorageAccount(
azureClient, targetStorageAccountType, targetStorageAccount, location, resourceGroupName,
template.getTemplateName(), tags
);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Got exception when checking the storage account for custom scripts", e);
}

int scriptLength = 0;
try {
BlobContainerClient container = getCloudBlobContainer(
azureClient, resourceGroupName, targetStorageAccount, Constants.CONFIG_CONTAINER_NAME,
template.retrieveAzureCloudReference().getAzureCredentialsId(),
template.isUseEntraIdForStorageAccount());
BlobClient blob = container.getBlobClient(targetScriptName);
scriptLength = localInitScript.getBytes(StandardCharsets.UTF_8).length;
blob.upload(BinaryData.fromString(localInitScript).toStream(), scriptLength, true);
return blob.getBlobUrl();
} catch (Exception e) {
throw AzureCloudException.create(
String.format("Failed to create Page Blob with script's length: %d", scriptLength), e);
}
}

public String uploadCustomScript(
AzureVMAgentTemplate template,
String targetScriptName) throws AzureCloudException {
return uploadCustomScript(template, targetScriptName, template.getInitScript());
}

/**
* Sets properties of virtual machine like IP and ssh port.
*
*/
public void setVirtualMachineDetails(
AzureVMAgent azureAgent, AzureVMAgentTemplate template) throws AzureCloudException {

VirtualMachine vm =
azureClient.virtualMachines().getByResourceGroup(template.getResourceGroupName(), azureAgent.getNodeName());

// Getting the first virtual IP
final PublicIpAddress publicIP = vm.getPrimaryPublicIPAddress();
String publicIPStr = "";
String privateIP = vm.getPrimaryNetworkInterface().primaryPrivateIP();
String fqdn;
if (publicIP == null) {
fqdn = privateIP;
LOGGER.log(Level.INFO, "The Azure agent doesn't have a public IP. Will use the private IP");
} else {
fqdn = publicIP.fqdn();
publicIPStr = publicIP.ipAddress();
}
azureAgent.setPublicDNSName(fqdn);
azureAgent.setSshPort(Constants.DEFAULT_SSH_PORT);
azureAgent.setPublicIP(publicIPStr);
azureAgent.setPrivateIP(privateIP);

LOGGER.log(Level.FINE, "Azure agent details:\n"
+ "nodeName={0}\n"
+ "adminUserName={1}\n"
+ "shutdownOnIdle={2}\n"
+ "retentionTimeInMin={3}\n"
+ "labels={4}",
new Object[]{azureAgent.getNodeName(), azureAgent.getVMCredentialsId(), azureAgent.isShutdownOnIdle(),
azureAgent.getRetentionTimeInMin(), azureAgent.getLabelString()});
}

public void attachPublicIP(AzureVMAgent azureAgent, AzureVMAgentTemplate template)
throws AzureCloudException {

VirtualMachine vm;
try {
vm = azureClient.virtualMachines().getByResourceGroup(template.getResourceGroupName(), azureAgent.getNodeName());
} catch (Exception e) {
throw AzureCloudException.create(e);
}

LOGGER.log(Level.INFO, "Trying to attach a public IP to the agent {0}", azureAgent.getNodeName());
if (vm != null) {
//check if the VM already has a public IP
if (vm.getPrimaryPublicIPAddress() == null) {
try {
vm.getPrimaryNetworkInterface()
.update()
.withNewPrimaryPublicIPAddress(
azureClient.publicIpAddresses()
.define(azureAgent.getNodeName() + "IPName")
.withRegion(template.getLocation())
.withExistingResourceGroup(template.getResourceGroupName())
.withLeafDomainLabel(azureAgent.getNodeName())
).apply();
} catch (Exception e) {
throw AzureCloudException.create(e);
}

setVirtualMachineDetails(azureAgent, template);
} else {
LOGGER.log(Level.INFO, "Agent {0} already has a public IP", azureAgent.getNodeName());
}
} else {
LOGGER.log(Level.WARNING, "Could not find agent {0} in Azure", azureAgent.getNodeName());
}
}

/**
* Determines whether a virtual machine exists.
*
* @param vmName Name of the VM.
* @param resourceGroupName Resource group of the VM.
* @return If the virtual machine exists
*/
private boolean virtualMachineExists(
String vmName,
String resourceGroupName) throws AzureCloudException {
LOGGER.log(Level.INFO, "Checking VM exists for {0}", vmName);

try {
azureClient.virtualMachines().getByResourceGroup(resourceGroupName, vmName);
} catch (ManagementException e) {
if (e.getResponse().getStatusCode() == 404) {
LOGGER.log(Level.INFO, "{0} doesn't exist", vmName);
return false;
}
throw e;
} catch (Exception e) {
throw AzureCloudException.create(e);
}

LOGGER.log(Level.FINE, "{0} exists", vmName);
return true;
}

/**
* Determines whether a given agent exists.
*
* @param agent to check
* @return True if the agent exists, false otherwise
*/
public static boolean virtualMachineExists(AzureVMAgent agent) {
try {
AzureVMManagementServiceDelegate delegate = agent.getServiceDelegate();
if (delegate != null) {
return delegate.virtualMachineExists(agent.getNodeName(), agent.getResourceGroupName());
} else {
return false;
}
} catch (AzureCloudException e) {
LOGGER.log(Level.WARNING, "Error while determining whether vm exists", e);
return false;
}
}

/**
* Creates Azure agent object with necessary info.
*
*/
public AzureVMAgent parseResponse(
ProvisioningActivity.Id id,
String vmname,
String deploymentName,
AzureVMAgentTemplate template,
OperatingSystemTypes osType) throws AzureCloudException {

try {
LOGGER.log(Level.INFO, "Deployment response: \n"
+ "\tfound agent {0}\n"
+ "\tOS type {1}\n"
+ "\tnumber of executors {2}",
new Object[]{vmname, osType, template.getNoOfParallelJobs()});

AzureVMCloud azureCloud = template.retrieveAzureCloudReference();

Map<String, Object> properties = AzureVMAgentTemplate.getTemplateProperties(template);

VirtualMachine vm =
azureClient.virtualMachines().getByResourceGroup(template.getResourceGroupName(), vmname);

final PublicIpAddress publicIP = vm.getPrimaryPublicIPAddress();
String privateIP = vm.getPrimaryNetworkInterface().primaryPrivateIP();
String fqdn;
if (publicIP == null) {
fqdn = privateIP;
} else {
fqdn = publicIP.fqdn();
}

return new AzureVMAgent(
id,
vmname,
template.getTemplateName(),
template.getTemplateDesc(),
osType,
template.getAgentWorkspace(),
(int) properties.get("noOfParallelJobs"),
template.getUsageMode(),
template.getLabels(),
template.retrieveAzureCloudReference().getDisplayName(),
template.getCredentialsId(),
null,
null,
template.getAdvancedImageInside().getSshConfig(),
(String) properties.get("jvmOptions"),
template.isShutdownOnIdle(),
false,
deploymentName,
template.getRetentionStrategy(),
(String) properties.get("initScript"),
(String) properties.get("terminateScript"),
azureCloud.getAzureCredentialsId(),
(String) properties.get("agentLaunchMethod"),
CleanUpAction.DEFAULT,
null,
template.getResourceGroupName(),
(Boolean) properties.get("executeInitScriptAsRoot"),
(Boolean) properties.get("doNotUseMachineIfInitFails"),
(Boolean) properties.get("enableMSI"),
(Boolean) properties.get("enableUAMI"),
(Boolean) properties.get("ephemeralOSDisk"),
(Boolean) properties.get("encryptionAtHost"),
(String) properties.get("uamiID"),
template,
fqdn,
template.getJavaPath(),
template.getRemotingOptions());
} catch (FormException | IOException e) {
throw AzureCloudException.create("Exception occurred while creating agent object", e);
}
}

/**
* Gets a map of available locations mapping display name -> name (usable in
* template).
*
*/
private static Set<String> getAvailableLocationsStandard() {
final Set<String> locations = new HashSet<>();
locations.add("UK South");
locations.add("UK West");
locations.add("East US");
locations.add("West US");
locations.add("South Central US");
locations.add("Central US");
locations.add("North Central US");
locations.add("North Europe");
locations.add("West Europe");
locations.add("Southeast Asia");
locations.add("East Asia");
locations.add("Japan West");
locations.add("Japan East");
locations.add("Brazil South");
locations.add("Australia Southeast");
locations.add("Australia East");
locations.add("Central India");
locations.add("South India");
locations.add("West India");
return locations;
}

private static Set<String> getAvailableLocationsChina() {
final Set<String> locations = new HashSet<>();
locations.add("China North");
locations.add("China East");
return locations;
}

/**
* Creates a map containing location -> vm role size list. This is hard
* coded and should be removed eventually once a transition to the 1.0.0 SDK
* is made
* <p>
* TODO: load SKU size from Azure dynamically
*
* @return New map
*/
private static Map<String, List<String>> getAvailableRoleSizes() {
final Map<String, List<String>> sizes = new HashMap<>();
sizes.put("East US", Arrays.asList(
"A10", "A11", "A5", "A6", "A7", "A8", "A9",
"Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4",
"Standard_A4", "Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1", "Standard_D1",
"Standard_D1_v2", "Standard_D11", "Standard_D11_v2", "Standard_D12", "Standard_D12_v2",
"Standard_D13", "Standard_D13_v2", "Standard_D14", "Standard_D14_v2",
"Standard_D2", "Standard_D2_v2", "Standard_D3", "Standard_D3_v2",
"Standard_D4", "Standard_D4_v2", "Standard_D5_v2",
"Standard_DS1", "Standard_DS1_v2", "Standard_DS11", "Standard_DS11_v2",
"Standard_DS12", "Standard_DS12_v2", "Standard_DS13", "Standard_DS13_v2",
"Standard_DS14", "Standard_DS14_v2", "Standard_DS2", "Standard_DS2_v2",
"Standard_DS3", "Standard_DS3_v2", "Standard_DS4", "Standard_DS4_v2",
"Standard_DS5_v2", "Standard_F1", "Standard_F16", "Standard_F16s",
"Standard_F1s", "Standard_F2", "Standard_F2s", "Standard_F4",
"Standard_F4s", "Standard_F8", "Standard_F8s"));
sizes.put("West US", Arrays.asList(
"A10", "A11", "A5", "A6", "A7", "A8", "A9",
"Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4",
"Standard_A4", "Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1",
"Standard_D1", "Standard_D1_v2", "Standard_D11", "Standard_D11_v2",
"Standard_D12", "Standard_D12_v2", "Standard_D13", "Standard_D13_v2",
"Standard_D14", "Standard_D14_v2", "Standard_D2", "Standard_D2_v2",
"Standard_D3", "Standard_D3_v2", "Standard_D4", "Standard_D4_v2",
"Standard_D5_v2", "Standard_DS1", "Standard_DS1_v2", "Standard_DS11", "Standard_DS11_v2",
"Standard_DS12", "Standard_DS12_v2", "Standard_DS13", "Standard_DS13_v2",
"Standard_DS14", "Standard_DS14_v2", "Standard_DS2", "Standard_DS2_v2",
"Standard_DS3", "Standard_DS3_v2", "Standard_DS4", "Standard_DS4_v2", "Standard_DS5_v2",
"Standard_F1", "Standard_F16", "Standard_F16s", "Standard_F1s", "Standard_F2", "Standard_F2s",
"Standard_F4", "Standard_F4s", "Standard_F8", "Standard_F8s",
"Standard_G1", "Standard_G2", "Standard_G3", "Standard_G4", "Standard_G5",
"Standard_GS1", "Standard_GS2", "Standard_GS3", "Standard_GS4", "Standard_GS5"));
sizes.put("South Central US", Arrays.asList(
"A10", "A11", "A5", "A6", "A7", "A8", "A9",
"Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4",
"Standard_A4", "Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1",
"Standard_D1", "Standard_D1_v2", "Standard_D11", "Standard_D11_v2",
"Standard_D12", "Standard_D12_v2", "Standard_D13", "Standard_D13_v2",
"Standard_D14", "Standard_D14_v2", "Standard_D2", "Standard_D2_v2",
"Standard_D3", "Standard_D3_v2", "Standard_D4", "Standard_D4_v2", "Standard_D5_v2",
"Standard_DS1", "Standard_DS1_v2", "Standard_DS11", "Standard_DS11_v2",
"Standard_DS12", "Standard_DS12_v2", "Standard_DS13", "Standard_DS13_v2",
"Standard_DS14", "Standard_DS14_v2", "Standard_DS2", "Standard_DS2_v2",
"Standard_DS3", "Standard_DS3_v2", "Standard_DS4", "Standard_DS4_v2",
"Standard_DS5_v2", "Standard_F1", "Standard_F16", "Standard_F16s",
"Standard_F1s", "Standard_F2", "Standard_F2s", "Standard_F4", "Standard_F4s",
"Standard_F8", "Standard_F8s"));
sizes.put("Central US", Arrays.asList(
"A5", "A6", "A7", "Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4",
"Standard_A4", "Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1",
"Standard_D1", "Standard_D1_v2", "Standard_D11", "Standard_D11_v2",
"Standard_D12", "Standard_D12_v2", "Standard_D13", "Standard_D13_v2",
"Standard_D14", "Standard_D14_v2", "Standard_D2", "Standard_D2_v2", "Standard_D3", "Standard_D3_v2",
"Standard_D4", "Standard_D4_v2", "Standard_D5_v2", "Standard_DS1", "Standard_DS1_v2",
"Standard_DS11", "Standard_DS11_v2", "Standard_DS12", "Standard_DS12_v2",
"Standard_DS13", "Standard_DS13_v2", "Standard_DS14", "Standard_DS14_v2",
"Standard_DS2", "Standard_DS2_v2", "Standard_DS3", "Standard_DS3_v2", "Standard_DS4", "Standard_DS4_v2",
"Standard_DS5_v2", "Standard_F1", "Standard_F16", "Standard_F16s",
"Standard_F1s", "Standard_F2", "Standard_F2s", "Standard_F4", "Standard_F4s",
"Standard_F8", "Standard_F8s"));
sizes.put("North Central US", Arrays.asList(
"A10", "A11", "A5", "A6", "A7", "A8", "A9",
"Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4",
"Standard_A4", "Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1",
"Standard_D1", "Standard_D1_v2", "Standard_D11", "Standard_D11_v2", "Standard_D12", "Standard_D12_v2",
"Standard_D13", "Standard_D13_v2", "Standard_D14", "Standard_D14_v2", "Standard_D2", "Standard_D2_v2",
"Standard_D3", "Standard_D3_v2", "Standard_D4", "Standard_D4_v2", "Standard_D5_v2",
"Standard_DS1_v2", "Standard_DS11_v2", "Standard_DS12_v2", "Standard_DS13_v2", "Standard_DS14_v2",
"Standard_DS2_v2", "Standard_DS3_v2", "Standard_DS4_v2", "Standard_DS5_v2",
"Standard_F1", "Standard_F16", "Standard_F16s", "Standard_F1s", "Standard_F2", "Standard_F2s",
"Standard_F4", "Standard_F4s", "Standard_F8", "Standard_F8s"));
sizes.put("East US 2", Arrays.asList(
"A5", "A6", "A7", "Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4",
"Standard_A4", "Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1",
"Standard_D1", "Standard_D1_v2", "Standard_D11", "Standard_D11_v2", "Standard_D12", "Standard_D12_v2",
"Standard_D13", "Standard_D13_v2", "Standard_D14", "Standard_D14_v2", "Standard_D2", "Standard_D2_v2",
"Standard_D3", "Standard_D3_v2", "Standard_D4", "Standard_D4_v2", "Standard_D5_v2",
"Standard_DS1", "Standard_DS1_v2", "Standard_DS11", "Standard_DS11_v2",
"Standard_DS12", "Standard_DS12_v2", "Standard_DS13", "Standard_DS13_v2",
"Standard_DS14", "Standard_DS14_v2", "Standard_DS2", "Standard_DS2_v2",
"Standard_DS3", "Standard_DS3_v2", "Standard_DS4", "Standard_DS4_v2", "Standard_DS5_v2",
"Standard_F1", "Standard_F16", "Standard_F16s", "Standard_F1s", "Standard_F2", "Standard_F2s",
"Standard_F4", "Standard_F4s", "Standard_F8", "Standard_F8s", "Standard_G1", "Standard_G2",
"Standard_G3", "Standard_G4", "Standard_G5", "Standard_GS1", "Standard_GS2",
"Standard_GS3", "Standard_GS4", "Standard_GS5"));
sizes.put("North Europe", Arrays.asList(
"A10", "A11", "A5", "A6", "A7", "A8", "A9",
"Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4",
"Standard_A4", "Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1",
"Standard_D1", "Standard_D1_v2", "Standard_D11", "Standard_D11_v2", "Standard_D12", "Standard_D12_v2",
"Standard_D13", "Standard_D13_v2", "Standard_D14", "Standard_D14_v2", "Standard_D2", "Standard_D2_v2",
"Standard_D3", "Standard_D3_v2", "Standard_D4", "Standard_D4_v2", "Standard_D5_v2",
"Standard_DS1", "Standard_DS1_v2", "Standard_DS11", "Standard_DS11_v2",
"Standard_DS12", "Standard_DS12_v2", "Standard_DS13", "Standard_DS13_v2",
"Standard_DS14", "Standard_DS14_v2", "Standard_DS2", "Standard_DS2_v2",
"Standard_DS3", "Standard_DS3_v2", "Standard_DS4", "Standard_DS4_v2", "Standard_DS5_v2",
"Standard_F1", "Standard_F16", "Standard_F16s", "Standard_F1s", "Standard_F2", "Standard_F2s",
"Standard_F4", "Standard_F4s", "Standard_F8", "Standard_F8s"));
sizes.put("West Europe", Arrays.asList(
"A10", "A11", "A5", "A6", "A7", "A8", "A9",
"Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4",
"Standard_A4", "Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1",
"Standard_D1", "Standard_D1_v2", "Standard_D11", "Standard_D11_v2", "Standard_D12", "Standard_D12_v2",
"Standard_D13", "Standard_D13_v2", "Standard_D14", "Standard_D14_v2", "Standard_D2", "Standard_D2_v2",
"Standard_D3", "Standard_D3_v2", "Standard_D4", "Standard_D4_v2", "Standard_D5_v2",
"Standard_DS1", "Standard_DS1_v2", "Standard_DS11", "Standard_DS11_v2",
"Standard_DS12", "Standard_DS12_v2", "Standard_DS13", "Standard_DS13_v2",
"Standard_DS14", "Standard_DS14_v2", "Standard_DS2", "Standard_DS2_v2",
"Standard_DS3", "Standard_DS3_v2", "Standard_DS4", "Standard_DS4_v2", "Standard_DS5_v2",
"Standard_F1", "Standard_F16", "Standard_F16s", "Standard_F1s", "Standard_F2", "Standard_F2s",
"Standard_F4", "Standard_F4s", "Standard_F8", "Standard_F8s", "Standard_G1", "Standard_G2",
"Standard_G3", "Standard_G4", "Standard_G5", "Standard_GS1", "Standard_GS2", "Standard_GS3",
"Standard_GS4", "Standard_GS5"));
sizes.put("Southeast Asia", Arrays.asList(
"A5", "A6", "A7", "Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4",
"Standard_A4", "Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1",
"Standard_D1", "Standard_D1_v2", "Standard_D11", "Standard_D11_v2",
"Standard_D12", "Standard_D12_v2", "Standard_D13", "Standard_D13_v2", "Standard_D14", "Standard_D14_v2",
"Standard_D2", "Standard_D2_v2", "Standard_D3", "Standard_D3_v2", "Standard_D4", "Standard_D4_v2",
"Standard_D5_v2", "Standard_DS1", "Standard_DS1_v2", "Standard_DS11", "Standard_DS11_v2",
"Standard_DS12", "Standard_DS12_v2", "Standard_DS13", "Standard_DS13_v2",
"Standard_DS14", "Standard_DS14_v2", "Standard_DS2", "Standard_DS2_v2",
"Standard_DS3", "Standard_DS3_v2", "Standard_DS4", "Standard_DS4_v2", "Standard_DS5_v2",
"Standard_F1", "Standard_F16", "Standard_F16s", "Standard_F1s", "Standard_F2", "Standard_F2s",
"Standard_F4", "Standard_F4s", "Standard_F8", "Standard_F8s", "Standard_G1", "Standard_G2",
"Standard_G3", "Standard_G4", "Standard_G5", "Standard_GS1", "Standard_GS2", "Standard_GS3",
"Standard_GS4", "Standard_GS5"));
sizes.put("East Asia", Arrays.asList(
"A5", "A6", "A7", "Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4",
"Standard_A4", "Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1",
"Standard_D1", "Standard_D1_v2", "Standard_D11", "Standard_D11_v2", "Standard_D12", "Standard_D12_v2",
"Standard_D13", "Standard_D13_v2", "Standard_D14", "Standard_D14_v2", "Standard_D2", "Standard_D2_v2",
"Standard_D3", "Standard_D3_v2", "Standard_D4", "Standard_D4_v2", "Standard_D5_v2", "Standard_DS1",
"Standard_DS11", "Standard_DS12", "Standard_DS13", "Standard_DS14", "Standard_DS2", "Standard_DS3",
"Standard_DS4", "Standard_F1", "Standard_F16", "Standard_F2", "Standard_F4", "Standard_F8"));
sizes.put("Japan West", Arrays.asList(
"A5", "A6", "A7", "Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4",
"Standard_A4", "Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1",
"Standard_D1", "Standard_D1_v2", "Standard_D11", "Standard_D11_v2", "Standard_D12", "Standard_D12_v2",
"Standard_D13", "Standard_D13_v2", "Standard_D14", "Standard_D14_v2", "Standard_D2", "Standard_D2_v2",
"Standard_D3", "Standard_D3_v2", "Standard_D4", "Standard_D4_v2", "Standard_D5_v2",
"Standard_DS1", "Standard_DS1_v2", "Standard_DS11", "Standard_DS11_v2",
"Standard_DS12", "Standard_DS12_v2", "Standard_DS13", "Standard_DS13_v2",
"Standard_DS14", "Standard_DS14_v2", "Standard_DS2", "Standard_DS2_v2",
"Standard_DS3", "Standard_DS3_v2", "Standard_DS4", "Standard_DS4_v2", "Standard_DS5_v2",
"Standard_F1", "Standard_F16", "Standard_F16s", "Standard_F1s", "Standard_F2", "Standard_F2s",
"Standard_F4", "Standard_F4s", "Standard_F8", "Standard_F8s"));
sizes.put("Japan East", Arrays.asList(
"A10", "A11", "A5", "A6", "A7", "A8", "A9", "Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4",
"Standard_A4", "Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1",
"Standard_D1", "Standard_D1_v2", "Standard_D11", "Standard_D11_v2", "Standard_D12", "Standard_D12_v2",
"Standard_D13", "Standard_D13_v2", "Standard_D14", "Standard_D14_v2", "Standard_D2", "Standard_D2_v2",
"Standard_D3", "Standard_D3_v2", "Standard_D4", "Standard_D4_v2", "Standard_D5_v2",
"Standard_DS1", "Standard_DS1_v2", "Standard_DS11", "Standard_DS11_v2",
"Standard_DS12", "Standard_DS12_v2", "Standard_DS13", "Standard_DS13_v2",
"Standard_DS14", "Standard_DS14_v2", "Standard_DS2", "Standard_DS2_v2",
"Standard_DS3", "Standard_DS3_v2", "Standard_DS4", "Standard_DS4_v2", "Standard_DS5_v2",
"Standard_F1", "Standard_F16", "Standard_F16s", "Standard_F1s", "Standard_F2", "Standard_F2s",
"Standard_F4", "Standard_F4s", "Standard_F8", "Standard_F8s"));
sizes.put("Brazil South", Arrays.asList(
"A5", "A6", "A7", "Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4",
"Standard_A4", "Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1",
"Standard_D1", "Standard_D1_v2", "Standard_D11", "Standard_D11_v2", "Standard_D12", "Standard_D12_v2",
"Standard_D13", "Standard_D13_v2", "Standard_D14", "Standard_D14_v2", "Standard_D2", "Standard_D2_v2",
"Standard_D3", "Standard_D3_v2", "Standard_D4", "Standard_D4_v2", "Standard_D5_v2",
"Standard_DS1_v2", "Standard_DS11_v2", "Standard_DS12_v2", "Standard_DS13_v2", "Standard_DS14_v2",
"Standard_DS2_v2", "Standard_DS3_v2", "Standard_DS4_v2", "Standard_DS5_v2",
"Standard_F1", "Standard_F16", "Standard_F16s", "Standard_F1s", "Standard_F2", "Standard_F2s",
"Standard_F4", "Standard_F4s", "Standard_F8", "Standard_F8s"));
sizes.put("Australia Southeast", Arrays.asList(
"A5", "A6", "A7", "Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4",
"Standard_A4", "Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1",
"Standard_D1", "Standard_D1_v2", "Standard_D11", "Standard_D11_v2", "Standard_D12", "Standard_D12_v2",
"Standard_D13", "Standard_D13_v2", "Standard_D14", "Standard_D14_v2", "Standard_D2", "Standard_D2_v2",
"Standard_D3", "Standard_D3_v2", "Standard_D4", "Standard_D4_v2", "Standard_D5_v2",
"Standard_DS1", "Standard_DS1_v2", "Standard_DS11", "Standard_DS11_v2",
"Standard_DS12", "Standard_DS12_v2", "Standard_DS13", "Standard_DS13_v2",
"Standard_DS14", "Standard_DS14_v2", "Standard_DS2", "Standard_DS2_v2",
"Standard_DS3", "Standard_DS3_v2", "Standard_DS4", "Standard_DS4_v2", "Standard_DS5_v2",
"Standard_F1", "Standard_F16", "Standard_F16s", "Standard_F1s", "Standard_F2", "Standard_F2s",
"Standard_F4", "Standard_F4s", "Standard_F8", "Standard_F8s"));
sizes.put("Australia East", Arrays.asList(
"A5", "A6", "A7", "Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4",
"Standard_A4", "Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1",
"Standard_D1", "Standard_D1_v2", "Standard_D11", "Standard_D11_v2", "Standard_D12", "Standard_D12_v2",
"Standard_D13", "Standard_D13_v2", "Standard_D14", "Standard_D14_v2", "Standard_D2", "Standard_D2_v2",
"Standard_D3", "Standard_D3_v2", "Standard_D4", "Standard_D4_v2", "Standard_D5_v2",
"Standard_DS1", "Standard_DS1_v2", "Standard_DS11", "Standard_DS11_v2",
"Standard_DS12", "Standard_DS12_v2", "Standard_DS13", "Standard_DS13_v2",
"Standard_DS14", "Standard_DS14_v2", "Standard_DS2", "Standard_DS2_v2",
"Standard_DS3", "Standard_DS3_v2", "Standard_DS4", "Standard_DS4_v2", "Standard_DS5_v2",
"Standard_F1", "Standard_F16", "Standard_F16s", "Standard_F1s", "Standard_F2", "Standard_F2s",
"Standard_F4", "Standard_F4s", "Standard_F8", "Standard_F8s", "Standard_G1", "Standard_G2",
"Standard_G3", "Standard_G4", "Standard_G5", "Standard_GS1", "Standard_GS2", "Standard_GS3",
"Standard_GS4", "Standard_GS5"));
sizes.put("Central India", Arrays.asList(
"A5", "A6", "A7", "Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4",
"Standard_A4", "Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1", "Standard_D1_v2",
"Standard_D11_v2", "Standard_D12_v2", "Standard_D13_v2", "Standard_D14_v2", "Standard_D2_v2",
"Standard_D3_v2", "Standard_D4_v2", "Standard_D5_v2", "Standard_DS1_v2", "Standard_DS11_v2",
"Standard_DS12_v2", "Standard_DS13_v2", "Standard_DS14_v2", "Standard_DS2_v2", "Standard_DS3_v2",
"Standard_DS4_v2", "Standard_DS5_v2", "Standard_F1", "Standard_F16", "Standard_F16s", "Standard_F1s",
"Standard_F2", "Standard_F2s", "Standard_F4", "Standard_F4s", "Standard_F8", "Standard_F8s"));
sizes.put("South India", Arrays.asList(
"A5", "A6", "A7", "Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4", "Standard_A4",
"Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1", "Standard_D1_v2", "Standard_D11_v2",
"Standard_D12_v2", "Standard_D13_v2", "Standard_D14_v2", "Standard_D2_v2", "Standard_D3_v2",
"Standard_D4_v2", "Standard_D5_v2", "Standard_DS1_v2", "Standard_DS11_v2", "Standard_DS12_v2",
"Standard_DS13_v2", "Standard_DS14_v2", "Standard_DS2_v2", "Standard_DS3_v2", "Standard_DS4_v2",
"Standard_DS5_v2", "Standard_F1", "Standard_F16", "Standard_F16s", "Standard_F1s", "Standard_F2",
"Standard_F2s", "Standard_F4", "Standard_F4s", "Standard_F8", "Standard_F8s"));
sizes.put("West India", Arrays.asList(
"A5", "A6", "A7", "Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4", "Standard_A4",
"Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1", "Standard_D1_v2", "Standard_D11_v2",
"Standard_D12_v2", "Standard_D13_v2", "Standard_D14_v2", "Standard_D2_v2", "Standard_D3_v2",
"Standard_D4_v2", "Standard_D5_v2", "Standard_F1", "Standard_F16", "Standard_F2", "Standard_F4",
"Standard_F8"));

// China sizes, may not be exact
sizes.put("China North", Arrays.asList(
"A5", "A6", "A7", "Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4",
"Standard_A4", "Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1",
"Standard_D1", "Standard_D1_v2", "Standard_D11", "Standard_D11_v2", "Standard_D12", "Standard_D12_v2",
"Standard_D13", "Standard_D13_v2", "Standard_D14", "Standard_D14_v2", "Standard_D2", "Standard_D2_v2",
"Standard_D3", "Standard_D3_v2", "Standard_D4", "Standard_D4_v2", "Standard_D5_v2",
"Standard_DS1", "Standard_DS1_v2", "Standard_DS11", "Standard_DS11_v2",
"Standard_DS12", "Standard_DS12_v2", "Standard_DS13", "Standard_DS13_v2",
"Standard_DS14", "Standard_DS14_v2", "Standard_DS2", "Standard_DS2_v2",
"Standard_DS3", "Standard_DS3_v2", "Standard_DS4", "Standard_DS4_v2", "Standard_DS5_v2",
"Standard_F1", "Standard_F16", "Standard_F16s", "Standard_F1s", "Standard_F2", "Standard_F2s",
"Standard_F4", "Standard_F4s", "Standard_F8", "Standard_F8s", "Standard_G1", "Standard_G2",
"Standard_G3", "Standard_G4", "Standard_G5", "Standard_GS1", "Standard_GS2", "Standard_GS3",
"Standard_GS4", "Standard_GS5"));
sizes.put("China East", Arrays.asList(
"A5", "A6", "A7", "Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4",
"Standard_A4", "Standard_A0", "Standard_A3", "Standard_A2", "Standard_A1",
"Standard_D1", "Standard_D1_v2", "Standard_D11", "Standard_D11_v2", "Standard_D12", "Standard_D12_v2",
"Standard_D13", "Standard_D13_v2", "Standard_D14", "Standard_D14_v2", "Standard_D2", "Standard_D2_v2",
"Standard_D3", "Standard_D3_v2", "Standard_D4", "Standard_D4_v2", "Standard_D5_v2",
"Standard_DS1", "Standard_DS11", "Standard_DS12", "Standard_DS13", "Standard_DS14",
"Standard_DS2", "Standard_DS3", "Standard_DS4", "Standard_F1", "Standard_F16", "Standard_F2",
"Standard_F4", "Standard_F8"));

return sizes;
}

private static Map<String, Map<String, String>> getDefaultImageProperties() {
final Map<String, Map<String, String>> imageProperties = new HashMap<>();
imageProperties.put(Constants.WINDOWS_SERVER_2016,
imageProperties("MicrosoftWindowsServer", "WindowsServer", "2016-Datacenter", "2016-Datacenter-with-Containers", Constants.OS_TYPE_WINDOWS));
imageProperties.put(Constants.WINDOWS_SERVER_2019,
imageProperties("MicrosoftWindowsServer", "WindowsServer", "2019-Datacenter", "2019-Datacenter-with-Containers", Constants.OS_TYPE_WINDOWS));
imageProperties.put(Constants.WINDOWS_SERVER_2022,
imageProperties("MicrosoftWindowsServer", "WindowsServer", "2022-datacenter-azure-edition-core", "2022-datacenter-azure-edition-core", Constants.OS_TYPE_WINDOWS));
imageProperties.put(Constants.UBUNTU_2004_LTS,
imageProperties("canonical", "0001-com-ubuntu-server-focal", "20_04-lts-gen2", "20_04-lts-gen2", Constants.OS_TYPE_LINUX));
imageProperties.put(Constants.UBUNTU_2204_LTS,
imageProperties("canonical", "0001-com-ubuntu-server-jammy", "22_04-lts-gen2", "22_04-lts-gen2", Constants.OS_TYPE_LINUX));
imageProperties.put(Constants.UBUNTU_2404_LTS,
imageProperties("canonical", "ubuntu-24_04-lts", "server", "server", Constants.OS_TYPE_LINUX));

return imageProperties;
}

private static Map<String, String> imageProperties(
String defaultImagePublisher,
String offer,
String sku,
String dockerImageSku,
String osType
) {
Map<String, String> properties = new HashMap<>();
properties.put(Constants.DEFAULT_IMAGE_PUBLISHER, defaultImagePublisher);
properties.put(Constants.DEFAULT_IMAGE_OFFER, offer);
properties.put(Constants.DEFAULT_IMAGE_SKU, sku);
properties.put(Constants.DEFAULT_DOCKER_IMAGE_SKU, dockerImageSku);
properties.put(Constants.DEFAULT_IMAGE_VERSION, "latest");
properties.put(Constants.DEFAULT_OS_TYPE, osType);
properties.put(Constants.DEFAULT_LAUNCH_METHOD, Constants.LAUNCH_METHOD_SSH);
return properties;
}

private static Map<String, Map<String, String>> getPreInstalledToolsScript() {
final Map<String, Map<String, String>> tools = new HashMap<>();
tools.put(Constants.WINDOWS_SERVER_2016, new HashMap<>());
tools.put(Constants.WINDOWS_SERVER_2019, new HashMap<>());
tools.put(Constants.WINDOWS_SERVER_2022, new HashMap<>());
tools.put(Constants.UBUNTU_2004_LTS, new HashMap<>());
tools.put(Constants.UBUNTU_2204_LTS, new HashMap<>());
tools.put(Constants.UBUNTU_2404_LTS, new HashMap<>());
try {
windows(Constants.WINDOWS_SERVER_2016, tools);
windows(Constants.WINDOWS_SERVER_2019, tools);
windows(Constants.WINDOWS_SERVER_2022, tools);
ubuntu(Constants.UBUNTU_2004_LTS, tools);
ubuntu(Constants.UBUNTU_2204_LTS, tools);
ubuntu(Constants.UBUNTU_2404_LTS, tools);

Check warning on line 1698 in src/main/java/com/microsoft/azure/vmagent/AzureVMManagementServiceDelegate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 539-1698 are not covered by tests

return tools;
} catch (IOException e) {
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/microsoft/azure/vmagent/util/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package com.microsoft.azure.vmagent.util;

import java.util.List;

public final class Constants {

public static final String PLUGIN_NAME = "AzureJenkinsVMAgent";
Expand Down Expand Up @@ -91,6 +93,7 @@
* Windows License Types.
*/
public static final String LICENSE_TYPE_NONE = "None";
public static final List<String> NO_LICENSE_TYPE = List.of(LICENSE_TYPE_NONE, "Classic");

Check warning on line 96 in src/main/java/com/microsoft/azure/vmagent/util/Constants.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 96 is not covered by tests

public static final String LICENSE_TYPE_WINDOWS_CLIENT = "Windows_Client";

Expand Down Expand Up @@ -130,6 +133,7 @@
public static final String WINDOWS_SERVER_2016 = "Windows Server 2016";
public static final String UBUNTU_2004_LTS = "Ubuntu 20.04 LTS";
public static final String UBUNTU_2204_LTS = "Ubuntu 22.04 LTS";
public static final String UBUNTU_2404_LTS = "Ubuntu 24.04 LTS";

/**
* ResourceGroup reference type.
Expand Down
Loading