Skip to content
Open
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
12 changes: 9 additions & 3 deletions oks_cli/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
ctx_update, set_cluster_id, get_cluster_id, get_project_id, \
get_template, get_cluster_name, format_changed_row, \
is_interesting_status, profile_completer, project_completer, \
kubeconfig_parse_fields, print_table, format_row
kubeconfig_parse_fields, print_table, format_row, apply_set_fields

from .profile import add_profile
from .project import project_create, project_login
Expand Down Expand Up @@ -371,8 +371,9 @@ def _create_cluster(project_name, cluster_config, output):
@click.option('--output', '-o', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
@click.option('--filename', '-f', type=click.File("r"), help="Path to file to use to create the cluster ")
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
@click.option('--set', 'set_fields', multiple=True, help="Set arbitrary nested fields, e.g. auth.oidc.issuer-url=value")
@click.pass_context
def cluster_create_command(ctx, project_name, cluster_name, description, admin, version, cidr_pods, cidr_service, control_plane, zone, enable_admission_plugins, disable_admission_plugins, quirk, tags, disable_api_termination, cp_multi_az, dry_run, output, filename, profile):
def cluster_create_command(ctx, project_name, cluster_name, description, admin, version, cidr_pods, cidr_service, control_plane, zone, enable_admission_plugins, disable_admission_plugins, quirk, tags, disable_api_termination, cp_multi_az, dry_run, output, filename, profile, set_fields):
"""CLI command to create a new Kubernetes cluster with optional configuration parameters."""
project_name, cluster_name, profile = ctx_update(ctx, project_name, cluster_name, profile)
login_profile(profile)
Expand Down Expand Up @@ -443,6 +444,8 @@ def cluster_create_command(ctx, project_name, cluster_name, description, admin,
if cp_multi_az is not None:
cluster_config["cp_multi_az"] = cp_multi_az

apply_set_fields(cluster_config, set_fields)

if not dry_run:
_create_cluster(project_name, cluster_config, output)
else:
Expand All @@ -466,8 +469,9 @@ def cluster_create_command(ctx, project_name, cluster_name, description, admin,
@click.option('--output', '-o', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
@click.option('--filename', '-f', type=click.File("r"), help="Path to file to use to update the cluster ")
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
@click.option('--set', 'set_fields', multiple=True, help="Set arbitrary nested fields, e.g. auth.oidc.issuer-url=value")
@click.pass_context
def cluster_update_command(ctx, project_name, cluster_name, description, admin, version, tags, enable_admission_plugins, disable_admission_plugins, quirk, disable_api_termination, control_plane, dry_run, output, filename, profile):
def cluster_update_command(ctx, project_name, cluster_name, description, admin, version, tags, enable_admission_plugins, disable_admission_plugins, quirk, disable_api_termination, control_plane, dry_run, output, filename, profile, set_fields):
"""CLI command to update an existing Kubernetes cluster with new configuration options."""
project_name, cluster_name, profile = ctx_update(ctx, project_name, cluster_name, profile)
login_profile(profile)
Expand Down Expand Up @@ -545,6 +549,8 @@ def cluster_update_command(ctx, project_name, cluster_name, description, admin,

if control_plane:
cluster_config['control_planes'] = control_plane

apply_set_fields(cluster_config, set_fields)

if dry_run:
print_output(cluster_config, output)
Expand Down
12 changes: 9 additions & 3 deletions oks_cli/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from .utils import do_request, print_output, print_table, find_project_id_by_name, get_project_id, set_project_id, \
detect_and_parse_input, transform_tuple, ctx_update, set_cluster_id, get_template, get_project_name, \
format_changed_row, is_interesting_status, login_profile, profile_completer, project_completer, \
format_row
format_row, apply_set_fields

# DEIFNE THE PROJECT COMMAND GROUP
@click.group(help="Project related commands.")
Expand Down Expand Up @@ -188,8 +188,9 @@ def project_list(ctx, project_name, deleted, plain, msword, uuid, watch, output,
@click.option('--output', '-o', type=click.Choice(["json", "yaml", "silent"]), help="Specify output format, by default is json")
@click.option('--filename', '-f', type=click.File("r"), help="Path to file to use to create the project")
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
@click.option('--set', 'set_fields', multiple=True, help="Set arbitrary nested fields, e.g. auth.oidc.issuer-url=value")
@click.pass_context
def project_create(ctx, project_name, description, cidr, quirk, tags, disable_api_termination, dry_run, output, filename, profile):
def project_create(ctx, project_name, description, cidr, quirk, tags, disable_api_termination, dry_run, output, filename, profile, set_fields):
"""Create a new project from options or file, with support for dry-run and output formatting."""
project_name, _, profile = ctx_update(ctx, project_name, None, profile)
login_profile(profile)
Expand Down Expand Up @@ -230,6 +231,8 @@ def project_create(ctx, project_name, description, cidr, quirk, tags, disable_ap

if disable_api_termination is not None:
project_config["disable_api_termination"] = disable_api_termination

apply_set_fields(project_config, set_fields)

if not dry_run:
data = do_request("POST", 'projects', json=project_config)
Expand Down Expand Up @@ -295,8 +298,9 @@ def project_delete_command(ctx, project_name, output, dry_run, force, profile):
@click.option('--output', '-o', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
@click.option('--dry-run', is_flag=True, help="Run without any action")
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
@click.option('--set', 'set_fields', multiple=True, help="Set arbitrary nested fields, e.g. auth.oidc.issuer-url=value")
@click.pass_context
def project_update_command(ctx, project_name, description, quirk, tags, disable_api_termination, output, dry_run, profile):
def project_update_command(ctx, project_name, description, quirk, tags, disable_api_termination, output, dry_run, profile, set_fields):
"""Update project details by name, supporting dry-run and output formatting."""
project_name, _, profile = ctx_update(ctx, project_name, None, profile)
login_profile(profile)
Expand Down Expand Up @@ -326,6 +330,8 @@ def project_update_command(ctx, project_name, description, quirk, tags, disable_
parsed_tags[key.strip()] = value.strip()

project_config['tags'] = parsed_tags

apply_set_fields(project_config, set_fields)

if dry_run:
print_output(project_config, output)
Expand Down
78 changes: 77 additions & 1 deletion oks_cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1070,4 +1070,80 @@ def format_changed_row(table, row):
def is_interesting_status(status):
"""Check if status is in the list of interesting statuses."""
interesting_statuses = ["pending", "deploying", "updating", "upgrading", "deleting"]
return status in interesting_statuses
return status in interesting_statuses

def normalize_key_path(key_path: str) -> str:
return re.sub(r'\[(\d+)\]', r'.\1', key_path)

def parse_value(value):
value = value.strip()

# Inline list: [a,b,c]
if value.startswith('[') and value.endswith(']'):
inner = value[1:-1].strip()
if not inner:
return []
return [parse_value(v.strip()) for v in inner.split(',')]

if value.lower() == "true":
return True
if value.lower() == "false":
return False

try:
return int(value)
except ValueError:
pass

try:
return float(value)
except ValueError:
pass

if ',' in value:
return [parse_value(v) for v in value.split(',')]

return value

def apply_set_fields(target: dict, set_fields):
for field in set_fields:
if '=' not in field:
raise click.ClickException(
f"Malformed --set argument: '{field}' (expected key=value)"
)

raw_key, value_str = field.split('=', 1)
key_path = normalize_key_path(raw_key)
key_parts = key_path.split('.')
value = parse_value(value_str)

current = target
for i, part in enumerate(key_parts[:-1]):
next_part = key_parts[i + 1]

is_index = part.isdigit()
next_is_index = next_part.isdigit()

if is_index:
idx = int(part)
if not isinstance(current, list):
current_parent = []
current[:] = current_parent # if needed
while len(current) <= idx:
current.append({})
current = current[idx]
else:
if part not in current:
current[part] = [] if next_is_index else {}
current = current[part]

last = key_parts[-1]
if last.isdigit():
idx = int(last)
if not isinstance(current, list):
current[last] = []
while len(current) <= idx:
current.append(None)
current[idx] = value
else:
current[last] = value