Skip to content
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
6 changes: 6 additions & 0 deletions src/azure-cli/azure/cli/command_modules/acs/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -1962,6 +1962,9 @@
- name: --gateway-prefix-size
type: int
short-summary: The size of Public IPPrefix attached to the Gateway-mode node pool. The node pool must be in Gateway mode.
- name: --localdns-config
type: string
short-summary: Set the localDNS Profile for a nodepool with a JSON config file.
- name: --workload-runtime
type: string
short-summary: Set the workload runtime.
Expand Down Expand Up @@ -2120,6 +2123,9 @@
- name: --if-none-match
type: string
short-summary: Set to '*' to allow a new node pool to be created, but to prevent updating an existing node pool. Other values will be ignored.
- name: --localdns-config
type: string
short-summary: Set the localDNS Profile for a nodepool with a JSON config file.
examples:
- name: Reconcile the nodepool back to its current state.
text: az aks nodepool update -g MyResourceGroup -n nodepool1 --cluster-name MyManagedCluster
Expand Down
18 changes: 18 additions & 0 deletions src/azure-cli/azure/cli/command_modules/acs/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,21 @@ def _get_id_from_shared_control_plane_identity(shared_identity) -> int:
)
)
return 0


def process_dns_overrides(overrides_dict, target_dict, build_override_func):
"""Helper function to safely process DNS overrides with null checks.
Processes DNS override dictionaries from LocalDNS configuration,
filtering out null values and applying the build function to valid entries.
:param overrides_dict: Dictionary containing DNS overrides (can be None)
:param target_dict: Target dictionary to populate with processed overrides
:param build_override_func: Function to build override objects from dict values
"""
if not isinstance(overrides_dict, dict):
raise InvalidArgumentValueError(
f"Expected a dictionary for DNS overrides, but got {type(overrides_dict).__name__}: {overrides_dict}"
)
if overrides_dict is not None:
for key, value in overrides_dict.items():
if value is not None:
target_dict[key] = build_override_func(value)
2 changes: 2 additions & 0 deletions src/azure-cli/azure/cli/command_modules/acs/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,7 @@ def load_arguments(self, _):
c.argument("if_none_match")
c.argument('gpu_driver', arg_type=get_enum_type(gpu_driver_install_modes))
c.argument("gateway_prefix_size", type=int, validator=validate_gateway_prefix_size)
c.argument('localdns_config', help='Path to a JSON file to configure the local DNS profile for a new nodepool.')
c.argument('workload_runtime', arg_type=get_enum_type(workload_runtime_types), help="The workload runtime to use on the nodepool.")

with self.argument_context('aks nodepool update', resource_type=ResourceType.MGMT_CONTAINERSERVICE, operation_group='agent_pools') as c:
Expand Down Expand Up @@ -1086,6 +1087,7 @@ def load_arguments(self, _):
c.argument('disable_secure_boot', action='store_true')
c.argument("if_match")
c.argument("if_none_match")
c.argument('localdns_config', help='Path to a JSON file to configure the local DNS profile for a new nodepool.')

with self.argument_context('aks nodepool upgrade') as c:
c.argument('max_surge', validator=validate_max_surge)
Expand Down
113 changes: 111 additions & 2 deletions src/azure-cli/azure/cli/command_modules/acs/agentpool_decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
DecoratorEarlyExitException,
DecoratorMode,
)
from azure.cli.command_modules.acs._helpers import get_snapshot_by_snapshot_id, safe_list_get
from azure.cli.command_modules.acs._helpers import get_snapshot_by_snapshot_id, safe_list_get, process_dns_overrides
from azure.cli.command_modules.acs._validators import extract_comma_separated_string
from azure.cli.command_modules.acs.base_decorator import BaseAKSContext, BaseAKSModels, BaseAKSParamDict
from azure.cli.core import AzCommandsLoader
Expand Down Expand Up @@ -1685,6 +1685,102 @@ def get_gateway_prefix_size(self) -> Union[int, None]:
"""
return self.raw_param.get('gateway_prefix_size')

def get_localdns_config(self):
return self.raw_param.get("localdns_config")

def get_localdns_profile(self):
"""
Returns the local DNS profile dict if set, or None.
Only supports loading from --localdns-config (JSON file).
Assumes the input is always a string filename.
"""
config = self.get_localdns_config()
if config:
if not isinstance(config, str) or not os.path.isfile(config):
raise InvalidArgumentValueError(
f"{config} is not a valid file, or not accessible."
)
profile = get_file_json(config)
if not isinstance(profile, dict):
raise InvalidArgumentValueError(
f"Error reading local DNS config from {config}. "
"Please provide a valid JSON file."
)
return profile
return None

def build_localdns_profile(self, agentpool: AgentPool) -> AgentPool:
"""Build local DNS profile for the AgentPool object if provided via --localdns-config."""
localdns_profile = self.get_localdns_profile()
kube_dns_overrides, vnet_dns_overrides = None, None

if localdns_profile is not None:
def find_keys_case_insensitive(dictionary, target_keys):
"""Find multiple keys case-insensitively and return a dict mapping target_key -> actual_key"""
result = {}
lowered_keys = {key.lower(): key for key in dictionary.keys()}
for target_key in target_keys:
lowered_target = target_key.lower()
if lowered_target in lowered_keys:
result[target_key] = lowered_keys[lowered_target]
else:
result[target_key] = None
return result

def build_override(override_dict):
if not isinstance(override_dict, dict):
raise InvalidArgumentValueError(
f"Expected a dictionary for DNS override settings,"
f" but got {type(override_dict).__name__}: {override_dict}"
)
camel_to_snake_case = {
"queryLogging": "query_logging",
"protocol": "protocol",
"forwardDestination": "forward_destination",
"forwardPolicy": "forward_policy",
"maxConcurrent": "max_concurrent",
"cacheDurationInSeconds": "cache_duration_in_seconds",
"serveStaleDurationInSeconds": "serve_stale_duration_in_seconds",
"serveStale": "serve_stale",
}
valid_keys = set(camel_to_snake_case.values())
filtered = {}
for k, v in override_dict.items():
if k in camel_to_snake_case:
filtered[camel_to_snake_case[k]] = v
elif k in valid_keys:
filtered[k] = v
return self.models.LocalDNSOverride(**filtered)

# Build kubeDNSOverrides and vnetDNSOverrides from the localdns_profile
key_mappings = find_keys_case_insensitive(localdns_profile, ["kubeDNSOverrides", "vnetDNSOverrides"])
actual_kube_key = key_mappings["kubeDNSOverrides"]
if actual_kube_key:
logger.debug("Found kubeDNSOverrides key as: %s", actual_kube_key)
kube_dns_overrides = {}
process_dns_overrides(
localdns_profile.get(actual_kube_key),
kube_dns_overrides,
build_override
)

actual_vnet_key = key_mappings["vnetDNSOverrides"]
if actual_vnet_key:
logger.debug("Found vnetDNSOverrides key as: %s", actual_vnet_key)
vnet_dns_overrides = {}
process_dns_overrides(
localdns_profile.get(actual_vnet_key),
vnet_dns_overrides,
build_override
)

agentpool.local_dns_profile = self.models.LocalDNSProfile(
mode=localdns_profile.get("mode"),
kube_dns_overrides=kube_dns_overrides,
vnet_dns_overrides=vnet_dns_overrides,
)
return agentpool

def get_workload_runtime(self) -> Union[str, None]:
"""Obtain the value of workload_runtime, default value is None.

Expand Down Expand Up @@ -2214,6 +2310,8 @@ def construct_agentpool_profile_default(self, bypass_restore_defaults: bool = Fa
agentpool = self.set_up_agentpool_gateway_profile(agentpool)
# set up virtual machines profile
agentpool = self.set_up_virtual_machines_profile(agentpool)
# set up local DNS profile
agentpool = self.set_up_localdns_profile(agentpool)
# set up workload_runtime
agentpool = self.set_up_workload_runtime(agentpool)
# restore defaults
Expand Down Expand Up @@ -2257,6 +2355,11 @@ def add_agentpool(self, agentpool: AgentPool) -> AgentPool:
headers=self.context.get_aks_custom_headers(),
)

def set_up_localdns_profile(self, agentpool: AgentPool) -> AgentPool:
"""Set up local DNS profile for the AgentPool object if provided via --localdns-config."""
self._ensure_agentpool(agentpool)
return self.context.build_localdns_profile(agentpool)


class AKSAgentPoolUpdateDecorator:
def __init__(
Expand Down Expand Up @@ -2543,11 +2646,12 @@ def update_agentpool_profile_default(self, agentpools: List[AgentPool] = None) -
agentpool = self.update_os_sku(agentpool)
# update fips image
agentpool = self.update_fips_image(agentpool)

# update vtpm
agentpool = self.update_vtpm(agentpool)
# update secure boot
agentpool = self.update_secure_boot(agentpool)
# update local DNS profile
agentpool = self.update_localdns_profile(agentpool)
return agentpool

def update_agentpool(self, agentpool: AgentPool) -> AgentPool:
Expand Down Expand Up @@ -2583,3 +2687,8 @@ def update_agentpool(self, agentpool: AgentPool) -> AgentPool:
if_none_match=self.context.get_if_none_match(),
headers=self.context.get_aks_custom_headers(),
)

def update_localdns_profile(self, agentpool: AgentPool) -> AgentPool:
"""Update local DNS profile for the AgentPool object if provided via --localdns-config."""
self._ensure_agentpool(agentpool)
return self.context.build_localdns_profile(agentpool)
4 changes: 4 additions & 0 deletions src/azure-cli/azure/cli/command_modules/acs/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -2867,6 +2867,8 @@ def aks_agentpool_add(
gpu_driver=None,
# static egress gateway - gateway-mode pool
gateway_prefix_size=None,
# local DNS
localdns_config=None,
):
# DO NOT MOVE: get all the original parameters and save them as a dictionary
raw_parameters = locals()
Expand Down Expand Up @@ -2927,6 +2929,8 @@ def aks_agentpool_update(
# etag headers
if_match=None,
if_none_match=None,
# local DNS
localdns_config=None,
):
# DO NOT MOVE: get all the original parameters and save them as a dictionary
raw_parameters = locals()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"mode": "Required",
"kubednsoverrides": {
".": {
"cacheDurationInSeconds": 3600,
"forwardDestination": "ClusterCoreDNS",
"forwardPolicy": "Sequential",
"maxConcurrent": 1000,
"protocol": "PreferUDP",
"queryLogging": "Error",
"serveStale": "Verify",
"serveStaleDurationInSeconds": 3600
},
"cluster.local": {
"cacheDurationInSeconds": 3600,
"forwardDestination": "ClusterCoreDNS",
"forwardPolicy": "Sequential",
"maxConcurrent": 1000,
"protocol": "ForceTCP",
"queryLogging": "Error",
"serveStale": "Immediate",
"serveStaleDurationInSeconds": 3600
}
},
"VNETDNSOVERRIDES": {
".": {
"cacheDurationInSeconds": 3600,
"forwardDestination": "VnetDNS",
"forwardPolicy": "Sequential",
"maxConcurrent": 1000,
"protocol": "PreferUDP",
"queryLogging": "Error",
"serveStale": "Verify",
"serveStaleDurationInSeconds": 3600
},
"cluster.local": {
"cacheDurationInSeconds": 3600,
"forwardDestination": "ClusterCoreDNS",
"forwardPolicy": "Sequential",
"maxConcurrent": 1000,
"protocol": "ForceTCP",
"queryLogging": "Error",
"serveStale": "Immediate",
"serveStaleDurationInSeconds": 3600
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"mode": "Required"
}
Loading
Loading