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

implement node labels #1346

Merged
merged 2 commits into from
Mar 4, 2020
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
3 changes: 3 additions & 0 deletions src/aks-preview/azext_aks_preview/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,9 @@
- name: --public-ip-per-vm
type: bool
short-summary: Each node will have a public ip.
- name: --labels
type: string
short-summary: The node labels for the node pool. You can't change the node labels through CLI after the node pool is created. See https://aka.ms/node-labels for syntax of labels.
"""

helps['aks nodepool scale'] = """
Expand Down
4 changes: 3 additions & 1 deletion src/aks-preview/azext_aks_preview/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
validate_nodepool_name, validate_vm_set_type, validate_load_balancer_sku,
validate_load_balancer_outbound_ips, validate_load_balancer_outbound_ip_prefixes,
validate_taints, validate_priority, validate_eviction_policy, validate_spot_max_price, validate_acr, validate_user,
validate_load_balancer_outbound_ports, validate_load_balancer_idle_timeout, validate_nodepool_tags)
validate_load_balancer_outbound_ports, validate_load_balancer_idle_timeout, validate_nodepool_tags, validate_nodepool_labels)
from ._consts import CONST_OUTBOUND_TYPE_LOAD_BALANCER, \
CONST_OUTBOUND_TYPE_USER_DEFINED_ROUTING, CONST_SCALE_SET_PRIORITY_REGULAR, CONST_SCALE_SET_PRIORITY_SPOT, \
CONST_SPOT_EVICTION_POLICY_DELETE, CONST_SPOT_EVICTION_POLICY_DEALLOCATE
Expand Down Expand Up @@ -53,6 +53,7 @@ def load_arguments(self, _):
c.argument('nodepool_name', type=str, default='nodepool1',
help='Node pool name, upto 12 alphanumeric characters', validator=validate_nodepool_name)
c.argument('nodepool_tags', nargs='*', validator=validate_nodepool_tags, help='space-separated tags: key[=value] [key[=value] ...]. Use "" to clear existing tags.')
c.argument('nodepool_labels', nargs='*', validator=validate_nodepool_labels, help='space-separated labels: key[=value] [key[=value] ...]. You can not change the node labels through CLI after creation. See https://aka.ms/node-labels for syntax of labels.')
c.argument('ssh_key_value', required=False, type=file_type, default=os.path.join('~', '.ssh', 'id_rsa.pub'),
completer=FilesCompleter(), validator=validate_ssh_key)
c.argument('aad_client_app_id')
Expand Down Expand Up @@ -138,6 +139,7 @@ def load_arguments(self, _):
c.argument('priority', arg_type=get_enum_type([CONST_SCALE_SET_PRIORITY_REGULAR, CONST_SCALE_SET_PRIORITY_SPOT]), validator=validate_priority)
c.argument('eviction_policy', arg_type=get_enum_type([CONST_SPOT_EVICTION_POLICY_DELETE, CONST_SPOT_EVICTION_POLICY_DEALLOCATE]), validator=validate_eviction_policy)
c.argument('spot_max_price', type=float, validator=validate_spot_max_price)
c.argument('labels', nargs='*', validator=validate_nodepool_labels)

for scope in ['aks nodepool show', 'aks nodepool delete', 'aks nodepool scale', 'aks nodepool upgrade', 'aks nodepool update']:
with self.argument_context(scope) as c:
Expand Down
72 changes: 72 additions & 0 deletions src/aks-preview/azext_aks_preview/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,75 @@ def _extract_cluster_autoscaler_params(namespace):
for item in namespace.cluster_autoscaler_profile:
params_dict.update(validate_tag(item))
namespace.cluster_autoscaler_profile = params_dict


def validate_nodepool_labels(namespace):
"""Validates that provided node labels is a valid format"""

if hasattr(namespace, 'nodepool_labels'):
labels = namespace.nodepool_labels
else:
labels = namespace.labels

if labels is None:
return

if isinstance(labels, list):
labels_dict = {}
for item in labels:
labels_dict.update(validate_label(item))
after_validation_labels = labels_dict
else:
after_validation_labels = validate_label(labels)

if hasattr(namespace, 'nodepool_labels'):
namespace.nodepool_labels = after_validation_labels
else:
namespace.labels = after_validation_labels


def validate_label(label):
"""Validates that provided label is a valid format"""
prefix_regex = re.compile(r"^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$")
name_regex = re.compile(r"^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$")
value_regex = re.compile(r"^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$")

if label == "":
return {}
kv = label.split('=')
if len(kv) != 2:
raise CLIError("Invalid label: %s. Label definition must be of format name=value." % label)
name_parts = kv[0].split('/')
if len(name_parts) == 1:
name = name_parts[0]
elif len(name_parts) == 2:
prefix = name_parts[0]
if not prefix or len(prefix) > 253:
raise CLIError("Invalid label: %s. Label prefix can't be empty or more than 253 chars." % label)
if not prefix_regex.match(prefix):
raise CLIError("Invalid label: %s. Prefix part a DNS-1123 label must consist of lower case alphanumeric "
"characters or '-', and must start and end with an alphanumeric character" % label)
name = name_parts[1]
else:
raise CLIError("Invalid label: %s. A qualified name must consist of alphanumeric characters, '-', '_' "
"or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or "
"'my.name', or '123-abc') with an optional DNS subdomain prefix and '/' "
"(e.g. 'example.com/MyName')" % label)

# validate label name
if not name or len(name) > 63:
raise CLIError("Invalid label: %s. Label name can't be empty or more than 63 chars." % label)
if not name_regex.match(name):
raise CLIError("Invalid label: %s. A qualified name must consist of alphanumeric characters, '-', '_' "
"or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or "
"'my.name', or '123-abc') with an optional DNS subdomain prefix and '/' (e.g. "
"'example.com/MyName')" % label)

# validate label value
if len(kv[1]) > 63:
raise CLIError("Invalid label: %s. Label must be more than 63 chars." % label)
if not value_regex.match(kv[1]):
raise CLIError("Invalid label: %s. A valid label must be an empty string or consist of alphanumeric "
"characters, '-', '_' or '.', and must start and end with an alphanumeric character" % label)

return {kv[0]: kv[1]}
4 changes: 4 additions & 0 deletions src/aks-preview/azext_aks_preview/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,7 @@ def aks_create(cmd, # pylint: disable=too-many-locals,too-many-statements,to
node_count=3,
nodepool_name="nodepool1",
nodepool_tags=None,
nodepool_labels=None,
service_principal=None, client_secret=None,
no_ssh_key=False,
disable_rbac=None,
Expand Down Expand Up @@ -779,6 +780,7 @@ def aks_create(cmd, # pylint: disable=too-many-locals,too-many-statements,to
agent_pool_profile = ManagedClusterAgentPoolProfile(
name=_trim_nodepoolname(nodepool_name), # Must be 12 chars or less before ACS RP adds to it
tags=nodepool_tags,
node_labels=nodepool_labels,
count=int(node_count),
vm_size=node_vm_size,
os_type="Linux",
Expand Down Expand Up @@ -2031,6 +2033,7 @@ def aks_agentpool_add(cmd, # pylint: disable=unused-argument,too-many-local
eviction_policy=CONST_SPOT_EVICTION_POLICY_DELETE,
spot_max_price=float('nan'),
public_ip_per_vm=False,
labels=None,
no_wait=False):
instances = client.list(resource_group_name, cluster_name)
for agentpool_profile in instances:
Expand All @@ -2057,6 +2060,7 @@ def aks_agentpool_add(cmd, # pylint: disable=unused-argument,too-many-local
agent_pool = AgentPool(
name=nodepool_name,
tags=tags,
node_labels=labels,
count=int(node_count),
vm_size=node_vm_size,
os_type=os_type,
Expand Down