Skip to content

Commit

Permalink
Merge pull request #109 from duplocloud/k8s-config-duplicate
Browse files Browse the repository at this point in the history
Kubeconfig Updater
  • Loading branch information
kferrone authored Oct 25, 2024
2 parents bdc7dbe + 53aac19 commit 0ad66d4
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 94 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
196 changes: 102 additions & 94 deletions src/duplo_resource/jit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand Down Expand Up @@ -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
}
Expand All @@ -413,15 +415,21 @@ def __context_config(self, ctx):
return {
"name": ctx["Name"],
"context": {
"cluster": ctx["Name"],
"cluster": ctx["PlanID"],
"user": ctx["Name"],
"namespace": ctx["DefaultNamespace"]
}
}

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)

def __empty_kubeconfig(self):
Expand Down
10 changes: 10 additions & 0 deletions src/duplocloud/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 0ad66d4

Please sign in to comment.