Add acs module support for version 2017-07-01 (#4010)

This commit is contained in:
Jingtao Ren 2017-07-13 10:43:38 -07:00 коммит произвёл Derek Bekoe
Родитель 168a6b4d78
Коммит 2a59cee5b6
9 изменённых файлов: 544 добавлений и 549 удалений

3
.gitignore поставляемый
Просмотреть файл

@ -88,3 +88,6 @@ artifacts/
# local Azure configuration
.config/
# Python version
.python-version

Просмотреть файл

@ -2,6 +2,11 @@
Release History
===============
unreleased
+++++++++++++++++++
* api version 2017-07-01 support
* update dcos and swarm to use latest api version instead of 2016-03-30
2.0.10 (2017-07-07)
+++++++++++++++++++
* minor fixes

Просмотреть файл

@ -29,3 +29,16 @@ helps['acs install-cli'] = """
type: command
short-summary: Download the DCOS/Kubernetes command line.
"""
helps['acs create'] = """
examples:
- name: Create a default acs cluster
text: az acs create -g MyResourceGroup -n MyContainerService
- name: Create a Kubernetes cluster
text: az acs create --orchestrator-type Kubernetes -g MyResourceGroup -n MyContainerService
- name: Create a Kubernetes cluster with ssh key provided
text: az acs create --orchestrator-type Kubernetes -g MyResourceGroup -n MyContainerService --ssh-key-value MySSHKeyValueOrPath
- name: Create a acs cluster with two agent pools
text: az acs create -g MyResourceGroup -n MyContainerService --agent-profiles "[{'name':'agentpool1'},{'name':'agentpool2'}]"
- name: Create a acs cluster with the second agent pool with vmSize specified
text: az acs create -g MyResourceGroup -n MyContainerService --agent-profiles "[{'name':'agentpool1'},{'name':'agentpool2','vmSize':'Standard_D2'}]"
"""

Просмотреть файл

@ -14,6 +14,7 @@ from azure.cli.core.commands import (
register_cli_argument,
register_extra_cli_argument)
from azure.cli.core.commands.parameters import tags_type
from azure.cli.core.commands.validators import validate_file_or_dict
from azure.cli.core.commands.parameters import (
enum_choice_list,
file_type,
@ -21,7 +22,7 @@ from azure.cli.core.commands.parameters import (
get_one_of_subscription_locations,
get_resource_name_completion_list)
from azure.mgmt.compute.containerservice.models import ContainerServiceOrchestratorTypes
from azure.cli.command_modules.acs._validators import validate_create_parameters, validate_ssh_key
from azure.cli.command_modules.acs._validators import validate_create_parameters, validate_ssh_key, validate_list_of_integers
def _compute_client_factory(**_):
@ -59,6 +60,8 @@ def _get_default_install_location(exe_name):
name_arg_type = CliArgumentType(options_list=('--name', '-n'), metavar='NAME')
storageProfileTypes = ["StorageAccount", "ManagedDisks"]
register_cli_argument('acs', 'tags', tags_type)
register_cli_argument('acs', 'name', arg_type=name_arg_type, configured_default='acs',
@ -70,6 +73,7 @@ register_cli_argument('acs', 'resource_group', arg_type=resource_group_name_type
register_cli_argument('acs', 'orchestrator_type', options_list=('--orchestrator-type', '-t'), **enum_choice_list(ContainerServiceOrchestratorTypes))
# some admin names are prohibited in acs, such as root, admin, etc. Because we have no control on the orchestrators, so default to a safe name.
register_cli_argument('acs', 'admin_username', options_list=('--admin-username',), default='azureuser', required=False)
register_cli_argument('acs', 'api_version', options_list=('--api-version',), required=False, help='Use API version of ACS to perform az acs operations. Available options: 2017-01-31, 2017-07-01. (2017-07-01 in preview, only in ukwest and uksouth). Default to use the latest version for the location')
register_cli_argument('acs', 'dns_name_prefix', options_list=('--dns-prefix', '-d'))
register_cli_argument('acs', 'container_service_name', options_list=('--name', '-n'), help='The name of the container service', completer=get_resource_name_completion_list('Microsoft.ContainerService/ContainerServices'))
@ -77,7 +81,18 @@ register_cli_argument('acs', 'ssh_key_value', required=False, help='SSH key file
register_cli_argument('acs create', 'name', arg_type=name_arg_type, validator=validate_ssh_key)
register_extra_cli_argument('acs create', 'generate_ssh_keys', action='store_true', help='Generate SSH public and private key files if missing', validator=validate_create_parameters)
register_cli_argument('acs create', 'master_profile', options_list=('--master-profile', '-m'), type=validate_file_or_dict, help='The file or dictionary representation of the master profile. Note it will override any master settings once set')
register_cli_argument('acs create', 'master_vm_size', completer=get_vm_size_completion_list)
register_cli_argument('acs create', 'master_osdisk_size', type=int, help='the disk size for master pool vms. Unit in GB. If not specified, the corresponding vmsize disk size will apply')
register_cli_argument('acs create', 'master_vnet_subnet_id', type=str, help='the custom vnet subnet id. Note agent need to used the same vnet if master set')
register_cli_argument('acs create', 'master_first_consecutive_static_ip', type=str, help='the first consecutive ip used to specify static ip block')
register_cli_argument('acs create', 'master_storage_profile', **enum_choice_list(storageProfileTypes))
register_cli_argument('acs create', 'agent_profiles', options_list=('--agent-profiles', '-a'), type=validate_file_or_dict, help='The file or dictionary representation of the agent profiles. Note it will override any agent settings once set')
register_cli_argument('acs create', 'agent_vm_size', completer=get_vm_size_completion_list)
register_cli_argument('acs create', 'agent_osdisk_size', type=int, help='the disk size for agent pool vms. Unit in GB. If not specified, the corresponding vmsize disk size will apply')
register_cli_argument('acs create', 'agent_vnet_subnet_id', type=str, help='the custom vnet subnet id. Note agent need to used the same vnet if master set')
register_cli_argument('acs create', 'agent_ports', type=validate_list_of_integers, help='the ports exposed on the agent pool, such as 8080,4000,80')
register_cli_argument('acs create', 'agent_storage_profile', **enum_choice_list(storageProfileTypes))
register_cli_argument('acs create', 'windows', action='store_true', help='If true, deploy a windows container cluster.')
register_cli_argument('acs create', 'validate', action='store_true', help='Generate and validate the ARM template without creating any resources')

Просмотреть файл

@ -42,6 +42,11 @@ def validate_ssh_key(namespace):
namespace.ssh_key_value = content
def validate_list_of_integers(string):
# extract comma separated list of integers
return list(map(int, string.split(',')))
def validate_create_parameters(namespace):
if not namespace.name:
raise CLIError('--name has no value')

Просмотреть файл

@ -130,8 +130,8 @@ class ACSClient(object):
:param command: Command to run on the remote host
:type command: String
:param background: True if command should be run in the foreground,
false to run it in a separate thread
:param background: True to run it in a separate thread,
False should be run in the foreground
:type command: Boolean
"""
if background:

Просмотреть файл

@ -368,9 +368,26 @@ def _get_subscription_id():
return sub_id
def acs_create(resource_group_name, deployment_name, name, ssh_key_value, dns_name_prefix=None, # pylint: disable=too-many-locals
admin_username="azureuser", agent_count=3,
agent_vm_size="Standard_D2_v2", location=None, master_count=1,
# pylint: disable=too-many-locals
# pylint: disable-msg=too-many-arguments
def acs_create(resource_group_name, deployment_name, name, ssh_key_value, dns_name_prefix=None,
location=None,
admin_username="azureuser",
api_version=None,
master_profile=None,
master_vm_size="Standard_D2_v2",
master_osdisk_size=0,
master_count=1,
master_vnet_subnet_id="",
master_first_consecutive_static_ip="",
master_storage_profile="",
agent_profiles=None,
agent_vm_size="Standard_D2_v2",
agent_osdisk_size=0,
agent_count=3,
agent_vnet_subnet_id="",
agent_ports=None,
agent_storage_profile="",
orchestrator_type="dcos", service_principal=None, client_secret=None, tags=None,
windows=False, admin_password="", generate_ssh_keys=False, # pylint: disable=unused-argument
validate=False, no_wait=False):
@ -396,16 +413,40 @@ def acs_create(resource_group_name, deployment_name, name, ssh_key_value, dns_na
:type content_version: str
:param admin_username: User name for the Linux Virtual Machines.
:type admin_username: str
:param api_version: ACS API version to use
:type api_version: str
:param master_profile: MasterProfile used to describe master pool
:type master_profile: dict
:param master_vm_size: The size of master pool Virtual Machine
:type master_vm_size: str
:param master_osdisk_size: The osDisk size in GB of master pool Virtual Machine
:type master_osdisk_size: int
:param master_count: The number of masters for the cluster.
:type master_count: int
:param master_vnet_subnet_id: The vnet subnet id for master pool
:type master_vnet_subnet_id: str
:param master_storage_profile: The storage profile used for master pool.
Possible value could be StorageAccount, ManagedDisk.
:type master_storage_profile: str
:param agent_profiles: AgentPoolProfiles used to describe agent pools
:type agent_profiles: dict
:param agent_count: The number of agents for the cluster. Note, for
DC/OS clusters you will also get 1 or 2 public agents in addition to
these selected masters.
:type agent_count: int
:param agent_vm_size: The size of the Virtual Machine.
:type agent_vm_size: str
:param agent_osdisk_size: The osDisk size in GB of agent pool Virtual Machine
:type agent_osdisk_size: int
:param agent_vnet_subnet_id: The vnet subnet id for master pool
:type agent_vnet_subnet_id: str
:param agent_ports: the ports exposed on the agent pool
:type agent_ports: list
:param agent_storage_profile: The storage profile used for agent pool.
Possible value could be StorageAccount, ManagedDisk.
:type agent_storage_profile: str
:param location: Location for VM resources.
:type location: str
:param master_count: The number of masters for the cluster.
:type master_count: int
:param orchestrator_type: The type of orchestrator used to manage the
applications on the cluster. Possible values include: 'dcos', 'swarm'
:type orchestrator_type: str or :class:`orchestratorType
@ -446,8 +487,20 @@ def acs_create(resource_group_name, deployment_name, name, ssh_key_value, dns_na
groups = _resource_client_factory().resource_groups
# Just do the get, we don't need the result, it will error out if the group doesn't exist.
rg = groups.get(resource_group_name)
if location is None:
location = rg.location # pylint:disable=no-member
if orchestrator_type == 'Kubernetes' or orchestrator_type == 'kubernetes':
# if api-version is not specified, or specified in a version not supported
# override based on location
if api_version is None or api_version not in ["2017-01-31", "2017-07-01"]:
# 2017-07-01 supported in the following two locations
if location in ["ukwest", "uksouth"]:
api_version = "2017-07-01"
# 2017-01-31 applied to other locations
else:
api_version = "2017-01-31"
if orchestrator_type.lower() == 'kubernetes':
# TODO: This really needs to be broken out and unit tested.
client = _graph_client_factory()
if not service_principal:
@ -477,21 +530,29 @@ def acs_create(resource_group_name, deployment_name, name, ssh_key_value, dns_na
raise CLIError('--client-secret is required if --service-principal is specified')
_validate_service_principal(client, service_principal)
return _create_kubernetes(resource_group_name, deployment_name, dns_name_prefix, name,
ssh_key_value, admin_username=admin_username,
agent_count=agent_count, agent_vm_size=agent_vm_size,
location=location, service_principal=service_principal,
client_secret=client_secret, master_count=master_count,
windows=windows, admin_password=admin_password,
validate=validate, no_wait=no_wait, tags=tags)
if windows:
elif windows:
raise CLIError('--windows is only supported for Kubernetes clusters')
if location is None:
location = rg.location # pylint:disable=no-member
return _create_non_kubernetes(resource_group_name, deployment_name, dns_name_prefix, name,
ssh_key_value, admin_username, agent_count, agent_vm_size, location,
orchestrator_type, master_count, tags, validate, no_wait)
return _create(resource_group_name, deployment_name, dns_name_prefix, name,
ssh_key_value, admin_username=admin_username,
api_version=api_version,
orchestrator_type=orchestrator_type,
master_profile=master_profile,
master_vm_size=master_vm_size,
master_osdisk_size=master_osdisk_size,
master_vnet_subnet_id=master_vnet_subnet_id,
master_first_consecutive_static_ip=master_first_consecutive_static_ip,
master_storage_profile=master_storage_profile,
agent_profiles=agent_profiles,
agent_count=agent_count, agent_vm_size=agent_vm_size,
agent_osdisk_size=agent_osdisk_size,
agent_vnet_subnet_id=agent_vnet_subnet_id,
agent_ports=agent_ports,
agent_storage_profile=agent_storage_profile,
location=location, service_principal=service_principal,
client_secret=client_secret, master_count=master_count,
windows=windows, admin_password=admin_password,
validate=validate, no_wait=no_wait, tags=tags)
def store_acs_service_principal(subscription_id, client_secret, service_principal,
@ -532,10 +593,15 @@ def load_acs_service_principals(config_path):
return None
def _create_kubernetes(resource_group_name, deployment_name, dns_name_prefix, name, ssh_key_value,
admin_username="azureuser", agent_count=3, agent_vm_size="Standard_D2_v2",
location=None, service_principal=None, client_secret=None, master_count=1,
windows=False, admin_password='', validate=False, no_wait=False, tags=None):
# pylint: disable-msg=too-many-arguments
def _create(resource_group_name, deployment_name, dns_name_prefix, name, ssh_key_value,
admin_username="azureuser", api_version=None, orchestrator_type="dcos",
master_profile=None, master_vm_size="Standard_D2_v2", master_osdisk_size=0, master_count=1,
master_vnet_subnet_id="", master_first_consecutive_static_ip="", master_storage_profile="",
agent_profiles=None, agent_count=3, agent_vm_size="Standard_D2_v2", agent_osdisk_size=0,
agent_vnet_subnet_id="", agent_ports=None, agent_storage_profile="",
location=None, service_principal=None, client_secret=None,
windows=False, admin_password='', validate=False, no_wait=False, tags=None):
if not location:
location = '[resourceGroup().location]'
windows_profile = None
@ -551,129 +617,136 @@ def _create_kubernetes(resource_group_name, deployment_name, dns_name_prefix, na
}
os_type = 'Windows'
if not agent_ports:
agent_ports = []
# The resources.properties fields should match with ContainerServices' api model
# So assumption:
# The API model created for new version should be compatible to use it in an older version
# There maybe additional field specified, but could be ignored by the older version
masterPoolProfile = {}
defaultMasterPoolProfile = {
"count": int(master_count),
"dnsPrefix": dns_name_prefix + 'mgmt',
}
if api_version == "2017-07-01":
defaultMasterPoolProfile = _update_dict(defaultMasterPoolProfile, {
"count": int(master_count),
"dnsPrefix": dns_name_prefix + 'mgmt',
"vmSize": master_vm_size,
"osDiskSizeGB": int(master_osdisk_size),
"vnetSubnetID": master_vnet_subnet_id,
"firstConsecutiveStaticIP": master_first_consecutive_static_ip,
"storageProfile": master_storage_profile,
})
if not master_profile:
masterPoolProfile = defaultMasterPoolProfile
else:
masterPoolProfile = _update_dict(defaultMasterPoolProfile, master_profile)
agentPoolProfiles = []
defaultAgentPoolProfile = {
"count": int(agent_count),
"vmSize": agent_vm_size,
"osType": os_type,
"dnsPrefix": dns_name_prefix + 'agent',
}
if api_version == "2017-07-01":
defaultAgentPoolProfile = _update_dict(defaultAgentPoolProfile, {
"count": int(agent_count),
"vmSize": agent_vm_size,
"osDiskSizeGB": int(agent_osdisk_size),
"osType": os_type,
"dnsPrefix": dns_name_prefix + 'agent',
"vnetSubnetID": agent_vnet_subnet_id,
"ports": agent_ports,
"storageProfile": agent_storage_profile,
})
if agent_profiles is None:
agentPoolProfiles.append(_update_dict(defaultAgentPoolProfile, {"name": "agentpool0"}))
else:
# override agentPoolProfiles by using the passed in agent_profiles
for idx, ap in enumerate(agent_profiles):
# if the user specified dnsPrefix, we honor that
# otherwise, we use the idx to avoid duplicate dns name
a = _update_dict({"dnsPrefix": dns_name_prefix + 'agent' + str(idx)}, ap)
agentPoolProfiles.append(_update_dict(defaultAgentPoolProfile, a))
# define outputs
outputs = {
"masterFQDN": {
"type": "string",
"value": "[reference(concat('Microsoft.ContainerService/containerServices/', '{}')).masterProfile.fqdn]".format(name) # pylint: disable=line-too-long
},
"sshMaster0": {
"type": "string",
"value": "[concat('ssh ', '{0}', '@', reference(concat('Microsoft.ContainerService/containerServices/', '{1}')).masterProfile.fqdn, ' -A -p 2200')]".format(admin_username, name) # pylint: disable=line-too-long
},
}
if orchestrator_type.lower() != "kubernetes":
outputs["agentFQDN"] = {
"type": "string",
"value": "[reference(concat('Microsoft.ContainerService/containerServices/', '{}')).agentPoolProfiles[0].fqdn]".format(name) # pylint: disable=line-too-long
}
properties = {
"orchestratorProfile": {
"orchestratorType": orchestrator_type,
},
"masterProfile": masterPoolProfile,
"agentPoolProfiles": agentPoolProfiles,
"linuxProfile": {
"ssh": {
"publicKeys": [
{
"keyData": ssh_key_value
}
]
},
"adminUsername": admin_username
},
}
if windows_profile is not None:
properties["windowsProfile"] = windows_profile
resource = {
"apiVersion": api_version,
"location": location,
"type": "Microsoft.ContainerService/containerServices",
"name": name,
"tags": tags,
"properties": properties,
}
template = {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"resources": [
resource,
],
"outputs": outputs,
}
params = {}
if service_principal is not None and client_secret is not None:
properties["servicePrincipalProfile"] = {
"clientId": service_principal,
"secret": "[parameters('clientSecret')]",
}
template["parameters"] = {
"clientSecret": {
"type": "secureString",
"metadata": {
"description": "The client secret for the service principal"
}
}
},
"resources": [
{
"apiVersion": "2017-01-31",
"location": location,
"type": "Microsoft.ContainerService/containerServices",
"name": name,
"tags": tags,
"properties": {
"orchestratorProfile": {
"orchestratorType": "kubernetes"
},
"masterProfile": {
"count": int(master_count),
"dnsPrefix": dns_name_prefix
},
"agentPoolProfiles": [
{
"name": "agentpools",
"count": int(agent_count),
"vmSize": agent_vm_size,
"dnsPrefix": dns_name_prefix + '-k8s-agents',
"osType": os_type,
}
],
"linuxProfile": {
"ssh": {
"publicKeys": [
{
"keyData": ssh_key_value
}
]
},
"adminUsername": admin_username
},
"windowsProfile": windows_profile,
"servicePrincipalProfile": {
"ClientId": service_principal,
"Secret": "[parameters('clientSecret')]"
}
}
}
]
}
params = {
"clientSecret": {
"value": client_secret
}
}
params = {
"clientSecret": {
"value": client_secret
}
}
return _invoke_deployment(resource_group_name, deployment_name, template, params, validate, no_wait)
def _create_non_kubernetes(resource_group_name, deployment_name, dns_name_prefix, name,
ssh_key_value, admin_username, agent_count, agent_vm_size, location,
orchestrator_type, master_count, tags, validate, no_wait):
template = {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"apiVersion": "2016-03-30",
"type": "Microsoft.ContainerService/containerServices",
"location": location,
"tags": tags,
"name": name,
"properties": {
"orchestratorProfile": {
"orchestratorType": orchestrator_type
},
"masterProfile": {
"count": int(master_count),
"dnsPrefix": dns_name_prefix + 'mgmt'
},
"agentPoolProfiles": [
{
"name": "agentpools",
"count": int(agent_count),
"vmSize": agent_vm_size,
"dnsPrefix": dns_name_prefix + 'agents'
}
],
"linuxProfile": {
"adminUsername": admin_username,
"ssh": {
"publicKeys": [
{
"keyData": ssh_key_value
}
]
}
}
}
}
],
"outputs": {
"masterFQDN": {
"type": "string",
"value": "[reference(concat('Microsoft.ContainerService/containerServices/', '{}')).masterProfile.fqdn]".format(name) # pylint: disable=line-too-long
},
"sshMaster0": {
"type": "string",
"value": "[concat('ssh ', '{0}', '@', reference(concat('Microsoft.ContainerService/containerServices/', '{1}')).masterProfile.fqdn, ' -A -p 2200')]".format(admin_username, name) # pylint: disable=line-too-long
},
"agentFQDN": {
"type": "string",
"value": "[reference(concat('Microsoft.ContainerService/containerServices/', '{}')).agentPoolProfiles[0].fqdn]".format(name) # pylint: disable=line-too-long
}
}
}
return _invoke_deployment(resource_group_name, deployment_name, template, {}, validate, no_wait)
def _invoke_deployment(resource_group_name, deployment_name, template, parameters, validate, no_wait):
from azure.mgmt.resource.resources import ResourceManagementClient
from azure.mgmt.resource.resources.models import DeploymentProperties
@ -1023,3 +1096,9 @@ def _get_object_stubs(graph_client, assignees):
params = GetObjectsParameters(include_directory_object_references=True,
object_ids=assignees)
return list(graph_client.objects.get_objects_by_object_ids(params))
def _update_dict(dict1, dict2):
cp = dict1.copy()
cp.update(dict2)
return cp

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -25,7 +25,7 @@ class AzureContainerServiceScenarioTest(ScenarioTest):
checks=[JMESPathCheck('properties.outputs.masterFQDN.value',
'{}mgmt.{}.cloudapp.azure.com'.format(dns_prefix, loc)),
JMESPathCheck('properties.outputs.agentFQDN.value',
'{}agents.{}.cloudapp.azure.com'.format(dns_prefix, loc))])
'{}agent.{}.cloudapp.azure.com'.format(dns_prefix, loc))])
# show
self.cmd('acs show -g {} -n {}'.format(resource_group, acs_name), checks=[
@ -45,13 +45,12 @@ class AzureContainerServiceScenarioTest(ScenarioTest):
checks=JMESPathCheck('agentPoolProfiles[0].count', 5))
# the length is set to avoid following error:
# Resource name k8s-master-ip-cliacstestdf9e19-clitest.rgbb2842ffee75a33f04366f72f08527c5157885b
# 80664c0560e06962ede3e78f0-00977c-7A54A2DE is invalid. The name can be up to 80 characters
# long.
@ResourceGroupPreparer(random_name_length=30, name_prefix='clitest')
# Resource name k8s-master-ip-cliacstestgae47e-clitestdqdcoaas25vlhygb2aktyv4-c10894mgmt-D811C917
# is invalid. The name can be up to 80 characters long.
@ResourceGroupPreparer(random_name_length=17, name_prefix='clitest')
@RoleBasedServicePrincipalPreparer()
def test_acs_create_kubernetes(self, resource_group, sp_name, sp_password):
acs_name = self.create_random_name('cliacstest', 16)
acs_name = self.create_random_name('acs', 14)
ssh_pubkey_file = self.generate_ssh_keys().replace('\\', '\\\\')
cmd = 'acs create -g {} -n {} --orchestrator-type Kubernetes --service-principal {} ' \
'--client-secret {} --ssh-key-value {}'