From 3b98c2c1f366568cc549e4bd99ec4504216b94bf Mon Sep 17 00:00:00 2001 From: Github Actions <4399427+kferrone@users.noreply.github.com> Date: Fri, 25 Oct 2024 12:26:23 -0600 Subject: [PATCH 1/2] updated kubeconfig updater --- CHANGELOG.md | 10 ++ src/duplo_resource/jit.py | 199 ++++++++++++++++++++------------------ src/duplocloud/client.py | 10 ++ 3 files changed, 125 insertions(+), 94 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d7baf4..f2b86b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + + - Update Kubeconfig now has a name argument to name the user/context anything you want. + - Update Kubeconfig will always name the server after the Plan. This will share the same server for all tenants in the same plan. Also prevents unnecessary duplicates of the same server. + - Update kubeconfig will update the sections instead of skipping if they already exist. For example you can switch to interactive mode. + +### Fixed + + - fixed the duplicating user section in the update kubeconfig command. + ## [0.2.37] - 2024-10-18 ### Fixed diff --git a/src/duplo_resource/jit.py b/src/duplo_resource/jit.py index f765e16..c1ab42e 100644 --- a/src/duplo_resource/jit.py +++ b/src/duplo_resource/jit.py @@ -11,6 +11,11 @@ from datetime import datetime import jwt +INSTALL_HINT = """ +Install duploctl for use with kubectl by following +https://cli.duplocloud.com/Jit/ +""" + @Resource("jit") class DuploJit(DuploResource): """Just In Time (JIT) Resource @@ -111,6 +116,84 @@ def aws(self, nocache: bool = None) -> dict: self.duplo.set_cached_item(k, sts) sts["Version"] = 1 return sts + + @Command() + def update_aws_config(self, + name: args.NAME) -> dict: + """Update AWS Config + + Update the AWS config file with a new profile. This will add a new profile to the AWS config file. + This will honor the `AWS_CONFIG_FILE` environment variable if it is set. + This will set the aws cli credentialprocess to use the `duploctl jit aws` command. + The generated command will inherit the `--host`, `--admin`, and `--interactive` flags from the current command. + + Usage: + ```sh + duploctl jit update_aws_config myprofile + ``` + + Example: Add Admin Profile + Run this command to add an admin profile. + ```sh + duploctl jit update_aws_config myportal --admin --interactive + ``` + This generates the following in the AWS config file. + ```toml + [profile myportal] + region = us-west-2 + credential_process = duploctl jit aws --host https://myportal.duplocloud.net --interactive --admin + ``` + + Args: + name: The name of the profile to add. + + Returns: + msg: The message that the profile was added. + """ + config = os.environ.get("AWS_CONFIG_FILE", f"{Path.home()}/.aws/config") + cp = configparser.ConfigParser() + cp.read(config) + + # If name is not provided, set default profile name to "duplo" + name = name or "duplo" + + prf = f'profile {name}' + msg = f"aws profile named {name} already exists in {config}" + try: + cp[prf] + except KeyError: + cmd = self.duplo.build_command("duploctl", "jit", "aws") + cp.add_section(prf) + cp.set(prf, 'region', os.getenv("AWS_DEFAULT_REGION", "us-west-2")) + cp.set(prf, 'credential_process', " ".join(cmd)) + with open(config, 'w') as configfile: + cp.write(configfile) + msg = f"aws profile named {name} was successfully added to {config}" + return {"message": msg} + + @Command() + def web(self) -> dict: + """Open Cloud Console + + Opens a default or specified browser to the Duploclouds underlying cloud. + Currently this only supports AWS. The global `--browser` flag can be used to specify a browser. + + Usage: + + ```sh + duploctl jit web --browser chrome + ``` + + Returns: + msg: The message that the browser is opening. + """ + b = self.duplo.browser + wb = webbrowser if not b else webbrowser.get(b) + sts = self.aws(nocache=True) + wb.open(sts["ConsoleUrl"], new=0, autoraise=True) + return { + "message": "Opening AWS console in browser" + } @Command() def k8s(self, @@ -153,6 +236,7 @@ def k8s(self, @Command() def update_kubeconfig(self, + name: args.NAME = None, planId: args.PLAN = None, save: bool = True) -> dict: """Update Kubeconfig @@ -209,16 +293,15 @@ def update_kubeconfig(self, else self.__empty_kubeconfig()) # load the cluster config info ctx = self.k8s_context(planId) + ctx["PlanID"] = planId or ctx["Name"].removeprefix("duploinfra-") + ctx["ARGS"] = ["jit", "k8s"] if self.duplo.isadmin: - ctx["Name"] = ctx["Name"].removeprefix("duploinfra-") - ctx["cmd_arg"] = "--plan" + ctx["Name"] = name or ctx["PlanID"] + ctx["ARGS"].extend(["--plan", ctx["PlanID"]]) + if self.duplo.tenant: + ctx["DefaultNamespace"] = f"duploservices-{self.duplo.tenant}" else: - if self.duplo.tenantid: - ctx["Name"] = self.duplo.tenantid - ctx["cmd_arg"] = "--tenant-id" - else: - ctx["Name"] = self.duplo.tenant - ctx["cmd_arg"] = "--tenant" + ctx["Name"] = name or self.duplo.tenantid or self.duplo.tenant # add the cluster, user, and context to the kubeconfig self.__add_to_kubeconfig("clusters", self.__cluster_config(ctx), kubeconfig) self.__add_to_kubeconfig("users", self.__user_config(ctx), kubeconfig) @@ -232,84 +315,6 @@ def update_kubeconfig(self, else: return kubeconfig - @Command() - def update_aws_config(self, - name: args.NAME) -> dict: - """Update AWS Config - - Update the AWS config file with a new profile. This will add a new profile to the AWS config file. - This will honor the `AWS_CONFIG_FILE` environment variable if it is set. - This will set the aws cli credentialprocess to use the `duploctl jit aws` command. - The generated command will inherit the `--host`, `--admin`, and `--interactive` flags from the current command. - - Usage: - ```sh - duploctl jit update_aws_config myprofile - ``` - - Example: Add Admin Profile - Run this command to add an admin profile. - ```sh - duploctl jit update_aws_config myportal --admin --interactive - ``` - This generates the following in the AWS config file. - ```toml - [profile myportal] - region = us-west-2 - credential_process = duploctl jit aws --host https://myportal.duplocloud.net --interactive --admin - ``` - - Args: - name: The name of the profile to add. - - Returns: - msg: The message that the profile was added. - """ - config = os.environ.get("AWS_CONFIG_FILE", f"{Path.home()}/.aws/config") - cp = configparser.ConfigParser() - cp.read(config) - - # If name is not provided, set default profile name to "duplo" - name = name or "duplo" - - prf = f'profile {name}' - msg = f"aws profile named {name} already exists in {config}" - try: - cp[prf] - except KeyError: - cmd = self.duplo.build_command("duploctl", "jit", "aws") - cp.add_section(prf) - cp.set(prf, 'region', os.getenv("AWS_DEFAULT_REGION", "us-west-2")) - cp.set(prf, 'credential_process', " ".join(cmd)) - with open(config, 'w') as configfile: - cp.write(configfile) - msg = f"aws profile named {name} was successfully added to {config}" - return {"message": msg} - - @Command() - def web(self) -> dict: - """Open Cloud Console - - Opens a default or specified browser to the Duploclouds underlying cloud. - Currently this only supports AWS. The global `--browser` flag can be used to specify a browser. - - Usage: - - ```sh - duploctl jit web --browser chrome - ``` - - Returns: - msg: The message that the browser is opening. - """ - b = self.duplo.browser - wb = webbrowser if not b else webbrowser.get(b) - sts = self.aws(nocache=True) - wb.open(sts["ConsoleUrl"], new=0, autoraise=True) - return { - "message": "Opening AWS console in browser" - } - @Command() def k8s_context(self, planId: args.PLAN = None) -> dict: @@ -386,22 +391,19 @@ def __cluster_config(self, ctx): else: cluster["insecure-skip-tls-verify"] = True return { - "name": ctx["Name"], + "name": ctx["PlanID"], "cluster": cluster } def __user_config(self, ctx): """Build a kubeconfig user object""" - cmd = self.duplo.build_command("jit", "k8s", ctx["cmd_arg"], ctx["Name"]) + cmd = self.duplo.build_command(*ctx["ARGS"]) return { "name": ctx["Name"], "user": { "exec": { "apiVersion": "client.authentication.k8s.io/v1beta1", - "installHint": """ -Install duploctl for use with kubectl by following -https://github.com/duplocloud/duploctl -""", + "installHint": INSTALL_HINT, "command": "duploctl", "args": cmd } @@ -413,7 +415,7 @@ def __context_config(self, ctx): return { "name": ctx["Name"], "context": { - "cluster": ctx["Name"], + "cluster": ctx["PlanID"], "user": ctx["Name"], "namespace": ctx["DefaultNamespace"] } @@ -421,8 +423,17 @@ def __context_config(self, ctx): def __add_to_kubeconfig(self, section, item, kubeconfig): """Add an item to a kubeconfig section if it is not already present""" - if item not in kubeconfig[section]: + exists = False + for i in kubeconfig[section]: + if i["name"] == item["name"]: + exists = True + i.update(item) + break + if not exists: kubeconfig[section].append(item) + # existing = next((i for i in kubeconfig[section] if i["name"] == item["name"]), None) + # if not existing: + # kubeconfig[section].append(item) def __empty_kubeconfig(self): return { diff --git a/src/duplocloud/client.py b/src/duplocloud/client.py index 4f51369..de7f974 100644 --- a/src/duplocloud/client.py +++ b/src/duplocloud/client.py @@ -635,10 +635,20 @@ def build_command(self, *args) -> list[str]: The context args as a dict. """ cmd = list(args) + # host is always needed cmd.append("--host") cmd.append(self.host) + # tenant name or id or not at all + if self.tenantid: + cmd.append("--tenant-id") + cmd.append(self.tenantid) + elif self.tenant: + cmd.append("--tenant") + cmd.append(self.tenant) + # only when admin if self.isadmin: cmd.append("--admin") + # interactive settings or token if self.interactive: cmd.append("--interactive") if self.nocache: From 53aac191803849fc533c53dba0461d1279575886 Mon Sep 17 00:00:00 2001 From: Github Actions <4399427+kferrone@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:11:55 -0600 Subject: [PATCH 2/2] removed dead code --- src/duplo_resource/jit.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/duplo_resource/jit.py b/src/duplo_resource/jit.py index c1ab42e..844082b 100644 --- a/src/duplo_resource/jit.py +++ b/src/duplo_resource/jit.py @@ -431,9 +431,6 @@ def __add_to_kubeconfig(self, section, item, kubeconfig): break if not exists: kubeconfig[section].append(item) - # existing = next((i for i in kubeconfig[section] if i["name"] == item["name"]), None) - # if not existing: - # kubeconfig[section].append(item) def __empty_kubeconfig(self): return {