package com.sequenceiq.cloudbreak.shell.commands.provider; import java.io.File; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.springframework.shell.core.CommandMarker; import org.springframework.shell.core.annotation.CliAvailabilityIndicator; import org.springframework.shell.core.annotation.CliCommand; import org.springframework.shell.core.annotation.CliOption; import com.sequenceiq.cloudbreak.api.model.AdjustmentType; import com.sequenceiq.cloudbreak.api.model.ArmAttachedStorageOption; import com.sequenceiq.cloudbreak.api.model.CredentialResponse; import com.sequenceiq.cloudbreak.api.model.FileSystemType; import com.sequenceiq.cloudbreak.api.model.OnFailureAction; import com.sequenceiq.cloudbreak.shell.commands.CredentialCommands; import com.sequenceiq.cloudbreak.shell.commands.InstanceGroupCommands; import com.sequenceiq.cloudbreak.shell.commands.NetworkCommands; import com.sequenceiq.cloudbreak.shell.commands.PlatformCommands; import com.sequenceiq.cloudbreak.shell.commands.SecurityGroupCommands; import com.sequenceiq.cloudbreak.shell.commands.StackCommands; import com.sequenceiq.cloudbreak.shell.commands.TemplateCommands; import com.sequenceiq.cloudbreak.shell.completion.ArmOrchestratorType; import com.sequenceiq.cloudbreak.shell.completion.AvailabilitySetName; import com.sequenceiq.cloudbreak.shell.completion.AzureInstanceType; import com.sequenceiq.cloudbreak.shell.completion.AzureVolumeType; import com.sequenceiq.cloudbreak.shell.completion.InstanceGroup; import com.sequenceiq.cloudbreak.shell.completion.InstanceGroupTemplateId; import com.sequenceiq.cloudbreak.shell.completion.InstanceGroupTemplateName; import com.sequenceiq.cloudbreak.shell.completion.PlatformVariant; import com.sequenceiq.cloudbreak.shell.completion.SecurityGroupId; import com.sequenceiq.cloudbreak.shell.completion.SecurityGroupName; import com.sequenceiq.cloudbreak.shell.completion.SecurityRules; import com.sequenceiq.cloudbreak.shell.completion.StackAvailabilityZone; import com.sequenceiq.cloudbreak.shell.completion.StackRegion; import com.sequenceiq.cloudbreak.shell.model.AvailabilitySetEntry; import com.sequenceiq.cloudbreak.shell.model.AvailabilitySetFaultDomainNumber; import com.sequenceiq.cloudbreak.shell.model.InstanceGroupEntry; import com.sequenceiq.cloudbreak.shell.model.ShellContext; import com.sequenceiq.cloudbreak.shell.util.TagParser; public class AzureCommands implements CommandMarker { public static final String PLATFORM = "AZURE"; public static final String SALT = "SALT"; private ShellContext shellContext; private CredentialCommands baseCredentialCommands; private NetworkCommands baseNetworkCommands; private SecurityGroupCommands baseSecurityGroupCommands; private TemplateCommands baseTemplateCommands; private PlatformCommands basePlatformCommands; private StackCommands stackCommands; private InstanceGroupCommands baseInstanceGroupCommands; public AzureCommands(ShellContext shellContext, CredentialCommands baseCredentialCommands, NetworkCommands baseNetworkCommands, SecurityGroupCommands baseSecurityGroupCommands, TemplateCommands baseTemplateCommands, PlatformCommands basePlatformCommands, StackCommands stackCommands, InstanceGroupCommands baseInstanceGroupCommands) { this.baseCredentialCommands = baseCredentialCommands; this.baseNetworkCommands = baseNetworkCommands; this.baseSecurityGroupCommands = baseSecurityGroupCommands; this.shellContext = shellContext; this.baseTemplateCommands = baseTemplateCommands; this.basePlatformCommands = basePlatformCommands; this.stackCommands = stackCommands; this.baseInstanceGroupCommands = baseInstanceGroupCommands; } @CliAvailabilityIndicator(value = "stack create --AZURE") public boolean createStackAvailable() { return stackCommands.createStackAvailable(PLATFORM) && shellContext.isPlatformAvailable(PLATFORM); } @CliAvailabilityIndicator(value = "template create --AZURE") public boolean createTemplateAvailable() { return baseTemplateCommands.createTemplateAvailable(PLATFORM) && shellContext.isPlatformAvailable(PLATFORM); } @CliAvailabilityIndicator(value = "platform create --AZURE") public boolean createPlatformAvailable() { return basePlatformCommands.createPlatformAvailable(PLATFORM) && shellContext.isPlatformAvailable(PLATFORM); } @CliAvailabilityIndicator(value = { "network create --AZURE --NEW", "network create --AZURE --EXISTING_SUBNET" }) public boolean createNetworkAvailable() { return baseNetworkCommands.createNetworkAvailable(PLATFORM) && shellContext.isPlatformAvailable(PLATFORM); } @CliAvailabilityIndicator(value = {"securitygroup create --AZURE --NEW"}) public boolean createSecurityGroupAvailable() { return baseSecurityGroupCommands.createSecurityGroupAvailable(PLATFORM) && shellContext.isPlatformAvailable(PLATFORM); } @CliAvailabilityIndicator(value = "credential create --AZURE") public boolean createCredentialAvailable() { return baseCredentialCommands.createCredentialAvailable(PLATFORM) && shellContext.isPlatformAvailable(PLATFORM); } @CliAvailabilityIndicator(value = "instancegroup configure --AZURE") public boolean configureInstanceGroupAvailable() { return baseInstanceGroupCommands.createInstanceGroupAvailable(PLATFORM) && shellContext.isPlatformAvailable(PLATFORM); } @CliAvailabilityIndicator(value = {"availabilityset list", "availabilityset create"}) public boolean configureAvailabilitySetAvailable() { return shellContext.isCredentialAvailable() && shellContext.isPlatformAvailable(PLATFORM) && shellContext.getActiveCloudPlatform().equals(PLATFORM); } @CliAvailabilityIndicator(value = {"availabilityset delete"}) public boolean configureAvailabilitySetModificationAvailable() { return !shellContext.getAzureAvailabilitySets().isEmpty(); } @CliCommand(value = "credential create --AZURE", help = "Create a new Azure credential") public String createCredential( @CliOption(key = "name", mandatory = true, help = "Name of the credential") String name, @CliOption(key = "subscriptionId", mandatory = true, help = "subscriptionId of the credential") String subscriptionId, @CliOption(key = "tenantId", mandatory = true, help = "tenantId of the credential") String tenantId, @CliOption(key = "appId", mandatory = true, help = "appId of the credential") String appId, @CliOption(key = "password", mandatory = true, help = "password of the credential") String password, @CliOption(key = "sshKeyPath", help = "sshKeyPath of the template") File sshKeyPath, @CliOption(key = "sshKeyUrl", help = "sshKeyUrl of the template") String sshKeyUrl, @CliOption(key = "sshKeyString", help = "Raw data of a public SSH key file") String sshKeyString, @CliOption(key = "publicInAccount", help = "flags if the credential is public in the account", unspecifiedDefaultValue = "false", specifiedDefaultValue = "true") boolean publicInAccount, @CliOption(key = "description", help = "Description of the credential") String description, @CliOption(key = "platformId", help = "Id of a platform the credential belongs to") Long platformId ) { Map<String, Object> parameters = new HashMap<>(); parameters.put("subscriptionId", subscriptionId); parameters.put("secretKey", password); parameters.put("tenantId", tenantId); parameters.put("accessKey", appId); return baseCredentialCommands.create(name, sshKeyPath, sshKeyUrl, sshKeyString, description, publicInAccount, platformId, parameters, PLATFORM); } @CliCommand(value = "network create --AZURE --NEW", help = "Create an Azure network configuration with a new network and a new subnet") public String createNewNetwork( @CliOption(key = "name", mandatory = true, help = "Name of the network") String name, @CliOption(key = "subnet", mandatory = true, help = "Subnet of the network in CIDR format") String subnet, @CliOption(key = "publicInAccount", help = "Marks the network as visible for all members of the account", unspecifiedDefaultValue = "false", specifiedDefaultValue = "true") boolean publicInAccount, @CliOption(key = "description", help = "Description of the network") String description, @CliOption(key = "platformId", help = "Id of a platform the network belongs to") Long platformId ) { Map<String, Object> parameters = new HashMap<>(); return baseNetworkCommands.create(name, subnet, publicInAccount, description, platformId, parameters, PLATFORM); } @CliCommand(value = "network create --AZURE --EXISTING_SUBNET", help = "Create an Azure network which uses an existing subnet in an existing network") public String createNetworkWithExistingSubnet( @CliOption(key = "name", mandatory = true, help = "Name of the network") String name, @CliOption(key = "resourceGroupName", mandatory = true, help = "Name of the custom resource group in case of existing virtual network and subnet") String rgName, @CliOption(key = "networkId", mandatory = true, help = "Name of the custom network within the custom resource group") String networkId, @CliOption(key = "subnetId", mandatory = true, help = "Name of the custom subnet within the custom resource group") String subnetId, @CliOption(key = "publicInAccount", help = "Marks the network as visible for all members of the account", unspecifiedDefaultValue = "false", specifiedDefaultValue = "true") boolean publicInAccount, @CliOption(key = "noPublicIp", help = "If true, no public IP is created for the instances") Boolean noPublicIp, @CliOption(key = "noFirewallRules", help = "If true, no new firewall rules will be created for the network") Boolean noFirewallRules, @CliOption(key = "description", help = "Description of the network") String description, @CliOption(key = "platformId", help = "Id of a platform the network belongs to") Long platformId ) { Map<String, Object> parameters = new HashMap<>(); if (rgName != null && networkId != null && subnetId != null) { parameters.put("resourceGroupName", rgName); parameters.put("networkId", networkId); parameters.put("subnetId", subnetId); } parameters.put("noPublicIp", noPublicIp != null ? noPublicIp : false); parameters.put("noFirewallRules", noFirewallRules != null ? noFirewallRules : false); return baseNetworkCommands.create(name, null, publicInAccount, description, platformId, parameters, PLATFORM); } @CliCommand(value = "securitygroup create --AZURE --NEW", help = "Create an AZURE security group") public String createNewSecurityGroup( @CliOption(key = "name", mandatory = true, help = "Name of the security group") String name, @CliOption(key = "description", help = "Description of the security group") String description, @CliOption(key = "rules", help = "Security rules in the following format: ';' separated list of <cidr>:<protocol>:<comma separated port list>") SecurityRules rules, @CliOption(key = "publicInAccount", help = "Marks the securitygroup as visible for all members of the account", specifiedDefaultValue = "true", unspecifiedDefaultValue = "false") Boolean publicInAccount) { return baseSecurityGroupCommands.create(name, description, null, PLATFORM, rules, publicInAccount); } @CliCommand(value = "template create --AZURE", help = "Create a new Azure template") public String createTemplate( @CliOption(key = "name", mandatory = true, help = "Name of the template") String name, @CliOption(key = "instanceType", mandatory = true, help = "type of the VM") AzureInstanceType instanceType, @CliOption(key = "volumeType", help = "volumeType of the template") AzureVolumeType volumeType, @CliOption(key = "volumeCount", mandatory = true, help = "volumeCount of the template") Integer volumeCount, @CliOption(key = "volumeSize", mandatory = true, help = "volumeSize(GB) of the template") Integer volumeSize, @CliOption(key = "publicInAccount", help = "flags if the template is public in the account", unspecifiedDefaultValue = "false", specifiedDefaultValue = "true") boolean publicInAccount, @CliOption(key = "description", help = "Description of the template") String description, @CliOption(key = "platformId", help = "Id of a platform the template belongs to") Long platformId ) { Map<String, Object> parameters = new HashMap<>(); return baseTemplateCommands.create(name, instanceType.getName(), volumeCount, volumeSize, volumeType.getName(), publicInAccount, description, parameters, platformId, PLATFORM); } @CliCommand(value = "cluster fileSystem --WASB", help = "Set Windows Azure Blob Storage filesystem on cluster") public String setWasbFileSystem( @CliOption(key = "defaultFileSystem", mandatory = true, help = "Use as default filesystem") Boolean defaultFileSystem, @CliOption(key = "accountName", mandatory = true, help = "name of the storage account") String accountName, @CliOption(key = "accountKey", mandatory = true, help = "primary access key to the storage account") String accountKey) { shellContext.setDefaultFileSystem(defaultFileSystem); shellContext.setFileSystemType(FileSystemType.WASB); Map<String, Object> props = new HashMap<>(); props.put("accountName", accountName); props.put("accountKey", accountKey); shellContext.setFileSystemParameters(props); return "Windows Azure Blob Storage filesystem configured"; } @CliCommand(value = "cluster fileSystem --ADLS", help = "Set Windows Azure Data Lake Storage filesystem on cluster") public String setAdlsFileSystem( //@CliOption(key = "defaultFileSystem", mandatory = false, specifiedDefaultValue = "false", unspecifiedDefaultValue = "false", // help = "Use as default filesystem") Boolean defaultFileSystem, @CliOption(key = "accountName", mandatory = true, help = "name of the storage account") String accountName) { shellContext.setDefaultFileSystem(false); shellContext.setFileSystemType(FileSystemType.ADLS); String credentialId = shellContext.getCredentialId(); CredentialResponse credential; try { credential = shellContext.getCredentialById(credentialId); } catch (Exception e) { throw shellContext.exceptionTransformer() .transformToRuntimeException(e); } Map<String, Object> parameters = credential.getParameters(); String tenantId = (String) parameters.get("tenantId"); String accessKey = (String) parameters.get("accessKey"); String secretKey = (String) parameters.get("secretKey"); Map<String, Object> props = new HashMap<>(); props.put("accountName", accountName); props.put("tenantId", tenantId); props.put("clientId", accessKey); props.put("credential", secretKey); if (accountName == null || tenantId == null || accessKey == null || secretKey == null) { throw shellContext.exceptionTransformer() .transformToRuntimeException("One or more required parameters for ADLS are not set!"); } shellContext.setFileSystemParameters(props); return "Windows Azure Data Lake Storage filesystem configured"; } @CliCommand(value = "platform create --AZURE", help = "Create a new Azure platform configuration") public String createPlatform( @CliOption(key = "name", mandatory = true, help = "Name of the platform") String name, @CliOption(key = "description", help = "Description of the platform") String description ) { try { return basePlatformCommands.create(name, description, PLATFORM, Collections.emptyMap()); } catch (Exception e) { throw shellContext.exceptionTransformer().transformToRuntimeException(e); } } @CliCommand(value = "availabilityset create", help = "Create an Azure availability set configuration") public String createAvailabilitySet( @CliOption(key = "name", mandatory = true, help = "Name of the availability set") String name, @CliOption(key = "platformFaultDomainCount", mandatory = true, help = "Number of fault domains") final AvailabilitySetFaultDomainNumber platformFaultDomainCount) { try { if (platformFaultDomainCount.number() > AvailabilitySetFaultDomainNumber.THREE.number() || platformFaultDomainCount.number() < AvailabilitySetFaultDomainNumber.TWO.number()) { throw shellContext.exceptionTransformer() .transformToRuntimeException("The number of fault domains must be between 2 and 3!"); } AvailabilitySetEntry as = new AvailabilitySetEntry(); as.setName(name); as.setFaultDomainCount(platformFaultDomainCount); shellContext.putAzureAvailabilitySet(name, as); return shellContext.outputTransformer().render(shellContext.getAzureAvailabilitySets(), "Availability sets"); } catch (Exception e) { throw shellContext.exceptionTransformer().transformToRuntimeException(e); } } @CliCommand(value = "availabilityset list", help = "List the Azure availability sets configured") public String listAvailabilitySet() { try { return shellContext.outputTransformer().render(shellContext.getAzureAvailabilitySets(), "Availability sets"); } catch (Exception e) { throw shellContext.exceptionTransformer().transformToRuntimeException(e); } } @CliCommand(value = "availabilityset delete", help = "Create an Azure availability set configuration") public String deleteAvailabilitySet( @CliOption(key = "name", mandatory = true, help = "Name of the availability set") String name) { try { AvailabilitySetEntry as = shellContext.getAzureAvailabilitySets().get(name); if (as != null) { shellContext.getAzureAvailabilitySets().remove(name); return String.format("Azure availability set deleted with %s name", name); } else { throw shellContext.exceptionTransformer().transformToRuntimeException(String.format("No availability set found with %s name", name)); } } catch (Exception e) { throw shellContext.exceptionTransformer().transformToRuntimeException(e); } } @CliCommand(value = "instancegroup configure --AZURE", help = "Configure instance groups") public String createInstanceGroup( @CliOption(key = "instanceGroup", mandatory = true, help = "Name of the instanceGroup") InstanceGroup instanceGroup, @CliOption(key = "nodecount", mandatory = true, help = "Nodecount for instanceGroup") Integer nodeCount, @CliOption(key = "ambariServer", mandatory = true, help = "Ambari server will be installed here if true") boolean ambariServer, @CliOption(key = "templateId", help = "TemplateId of the instanceGroup") InstanceGroupTemplateId instanceGroupTemplateId, @CliOption(key = "templateName", help = "TemplateName of the instanceGroup") InstanceGroupTemplateName instanceGroupTemplateName, @CliOption(key = "securityGroupId", help = "SecurityGroupId of the instanceGroup") SecurityGroupId instanceGroupSecurityGroupId, @CliOption(key = "securityGroupName", help = "SecurityGroupName of the instanceGroup") SecurityGroupName instanceGroupSecurityGroupName, @CliOption(key = "availabilitySetName", help = "Availability set name for the instanceGroup") AvailabilitySetName availabilitySetName) throws Exception { Map<String, Object> parameters = new HashMap<>(); if (availabilitySetName != null) { String asName = availabilitySetName.getName(); for (String otherIgName : shellContext.getInstanceGroups().keySet()) { InstanceGroupEntry otherIgEntry = shellContext.getInstanceGroups().get(otherIgName); if (instanceGroup.getName().equals(otherIgName)) { continue; } if (otherIgEntry.getAttributes() != null && otherIgEntry.getAttributes().get("availabilitySet") != null) { Object otherIgAs = otherIgEntry.getAttributes().get("availabilitySet"); if (otherIgAs != null && otherIgAs instanceof HashMap) { String otherIgAsName = (String) ((HashMap) otherIgAs).get("name"); if (asName.equals(otherIgAsName)) { throw shellContext.exceptionTransformer() .transformToRuntimeException("Cannot use the same availability set for two different instance groups!"); } } } } AvailabilitySetEntry as = shellContext.getAzureAvailabilitySets().get(availabilitySetName.getName()); if (as == null) { throw shellContext.exceptionTransformer() .transformToRuntimeException("There is no availability set defined with the name " + availabilitySetName.getName()); } Map<String, Object> map = new HashMap<String, Object>(); map.put("name", as.getName()); map.put("faultDomainCount", as.getFaultDomainCount().number()); parameters.put("availabilitySet", map); } return baseInstanceGroupCommands.create(instanceGroup, nodeCount, ambariServer, instanceGroupTemplateId, instanceGroupTemplateName, instanceGroupSecurityGroupId, instanceGroupSecurityGroupName, parameters); } @CliCommand(value = "stack create --AZURE", help = "Create a new Azure stack based on a template") public String create( @CliOption(key = "name", mandatory = true, help = "Name of the stack") String name, @CliOption(key = "region", mandatory = true, help = "region of the stack") StackRegion region, @CliOption(key = "availabilityZone", help = "availabilityZone of the stack") StackAvailabilityZone availabilityZone, @CliOption(key = "publicInAccount", help = "marks the stack as visible for all members of the account", unspecifiedDefaultValue = "false", specifiedDefaultValue = "true") boolean publicInAccount, @CliOption(key = "onFailureAction", help = "onFailureAction which is ROLLBACK or DO_NOTHING.") OnFailureAction onFailureAction, @CliOption(key = "adjustmentType", help = "adjustmentType which is EXACT or PERCENTAGE.") AdjustmentType adjustmentType, @CliOption(key = "ambariVersion", help = "Ambari version") String ambariVersion, @CliOption(key = "hdpVersion", help = "HDP version") String hdpVersion, @CliOption(key = "imageCatalog", help = "custom image catalog URL") String imageCatalog, @CliOption(key = "threshold", help = "threshold of failure") Long threshold, @CliOption(key = "diskPerStorage", help = "disk per Storage Account on Azure") Integer diskPerStorage, @CliOption(key = "platformVariant", help = "select platform variant version") PlatformVariant platformVariant, @CliOption(key = "relocateDocker", help = "relocate docker in startup time") Boolean relocateDocker, @CliOption(key = "tags", help = "created resources will be tagged with these key=value pairs, format: key1=value1,key2=value2") String tags, @CliOption(key = "orchestrator", help = "select orchestrator variant version") ArmOrchestratorType orchestratorType, @CliOption(key = "attachedStorageType", help = "type of the storage creation") ArmAttachedStorageOption attachedStorageOption, @CliOption(key = "persistentStorage", help = "name of the persistent storage") String persistentStorage, @CliOption(key = "customImage", help = "select customImage for cluster") String customImage, @CliOption(key = "wait", help = "Wait for stack creation", unspecifiedDefaultValue = "false", specifiedDefaultValue = "true") boolean wait, @CliOption(key = "timeout", help = "Wait timeout if wait=true", mandatory = false) Long timeout) { orchestratorType = (orchestratorType == null) ? new ArmOrchestratorType(SALT) : orchestratorType; if (SALT.equals(orchestratorType.getName())) { relocateDocker = relocateDocker == null ? false : relocateDocker; if (relocateDocker) { throw shellContext.exceptionTransformer() .transformToRuntimeException("Relocate docker could not be 'yes' if you are not using containers in the cluster"); } } else { relocateDocker = relocateDocker == null ? true : relocateDocker; } Map<String, String> params = new HashMap<>(); if (diskPerStorage != null) { params.put("diskPerStorage", diskPerStorage.toString()); } if (attachedStorageOption != null && shellContext.isAzureActiveCredential()) { params.put("attachedStorageOption", attachedStorageOption.name()); } else if (shellContext.isAzureActiveCredential()) { params.put("attachedStorageOption", ArmAttachedStorageOption.SINGLE.name()); } if (persistentStorage != null && shellContext.isAzureActiveCredential()) { params.put("persistentStorage", persistentStorage); } else if (shellContext.isAzureActiveCredential()) { params.put("persistentStorage", "cbstore"); } return stackCommands.create(name, region, availabilityZone, publicInAccount, onFailureAction, adjustmentType, threshold, relocateDocker, wait, platformVariant, orchestratorType.getName(), PLATFORM, ambariVersion, hdpVersion, imageCatalog, params, TagParser.parseTagsIntoMap(tags), customImage, timeout); } }