-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* kubectl command wrapper * EOL * get fresh credentials when spawning the shell too * no need to override these methods * development environment explanation * kc alias * configure command * refresh credentials on each run * don't assume the position of the aws eks command * refresh_aws_credentials to support both SSO and MFA * tests! * doc for the command
- Loading branch information
Showing
12 changed files
with
377 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,4 +13,6 @@ leverage.egg-info | |
**/__pycache__* | ||
.pytest_cache | ||
coverage | ||
.coverage | ||
.coverage | ||
|
||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import os | ||
import pwd | ||
from pathlib import Path | ||
|
||
from click.exceptions import Exit | ||
from docker.types import Mount | ||
|
||
from leverage import logger | ||
from leverage._utils import chain_commands, EmptyEntryPoint, refresh_aws_credentials | ||
from leverage.container import TerraformContainer | ||
|
||
|
||
class KubeCtlContainer(TerraformContainer): | ||
"""Container specifically tailored to run kubectl commands.""" | ||
|
||
KUBECTL_CLI_BINARY = "/usr/local/bin/kubectl" | ||
KUBECTL_CONFIG_PATH = Path("/root/.kube") | ||
KUBECTL_CONFIG_FILE = KUBECTL_CONFIG_PATH / Path("config") | ||
|
||
def __init__(self, client): | ||
super().__init__(client) | ||
|
||
self.entrypoint = self.KUBECTL_CLI_BINARY | ||
|
||
host_config_path = str(Path.home() / Path(f".kube/{self.project}")) | ||
self.container_config["host_config"]["Mounts"].append( | ||
# the container is expecting a file named "config" here | ||
Mount( | ||
source=host_config_path, | ||
target=str(self.KUBECTL_CONFIG_PATH), | ||
type="bind", | ||
) | ||
) | ||
|
||
@refresh_aws_credentials | ||
def start_shell(self): | ||
with EmptyEntryPoint(self): | ||
self._start() | ||
|
||
@refresh_aws_credentials | ||
def configure(self): | ||
# make sure we are on the cluster layer | ||
self.check_for_layer_location() | ||
|
||
logger.info("Retrieving k8s cluster information...") | ||
with EmptyEntryPoint(self): | ||
# generate the command that will configure the new cluster | ||
add_eks_cluster_cmd = self._get_eks_kube_config() | ||
# and the command that will set the proper ownership on the config file (otherwise the owner will be "root") | ||
change_owner_cmd = self._change_kube_file_owner_cmd() | ||
full_cmd = chain_commands([add_eks_cluster_cmd, change_owner_cmd]) | ||
|
||
logger.info("Configuring context...") | ||
with EmptyEntryPoint(self): | ||
# we use _start here because in the case of MFA it will ask for the token | ||
exit_code = self._start(full_cmd) | ||
if exit_code: | ||
raise Exit(exit_code) | ||
|
||
logger.info("Done.") | ||
|
||
def _get_eks_kube_config(self) -> str: | ||
exit_code, output = self._exec(f"{self.TF_BINARY} output") | ||
if exit_code: | ||
logger.error(output) | ||
raise Exit(exit_code) | ||
|
||
aws_eks_cmd = next(op for op in output.split("\n") if op.startswith("aws eks update-kubeconfig")) | ||
# assuming the cluster container is on the primary region | ||
return aws_eks_cmd + f" --region {self.common_conf['region_primary']}" | ||
|
||
def _get_user_group_id(self, user_id) -> int: | ||
user = pwd.getpwuid(user_id) | ||
return user.pw_gid | ||
|
||
def _change_kube_file_owner_cmd(self) -> str: | ||
user_id = os.getuid() | ||
group_id = self._get_user_group_id(user_id) | ||
|
||
return f"chown {user_id}:{group_id} {self.KUBECTL_CONFIG_FILE}" | ||
|
||
def check_for_layer_location(self): | ||
super(KubeCtlContainer, self).check_for_layer_location() | ||
# assuming the "cluster" layer will contain the expected EKS outputs | ||
if self.cwd.parts[-1] != "cluster": | ||
logger.error("This command can only run at the [bold]cluster layer[/bold].") | ||
raise Exit(1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
from leverage._internals import pass_state | ||
from leverage._internals import pass_container | ||
from leverage.container import get_docker_client | ||
from leverage.containers.kubectl import KubeCtlContainer | ||
|
||
import click | ||
|
||
from leverage.modules.utils import _handle_subcommand | ||
|
||
CONTEXT_SETTINGS = {"ignore_unknown_options": True} | ||
|
||
|
||
@click.group(invoke_without_command=True, context_settings={"ignore_unknown_options": True}) | ||
@click.argument("args", nargs=-1, type=click.UNPROCESSED) | ||
@pass_state | ||
@click.pass_context | ||
def kubectl(context, state, args): | ||
"""Run Kubectl commands in a custom containerized environment.""" | ||
state.container = KubeCtlContainer(get_docker_client()) | ||
state.container.ensure_image() | ||
_handle_subcommand(context=context, cli_container=state.container, args=args) | ||
|
||
|
||
@kubectl.command(context_settings=CONTEXT_SETTINGS) | ||
@pass_container | ||
def shell(kctl: KubeCtlContainer): | ||
"""Spawn a shell with the kubectl credentials pre-configured.""" | ||
kctl.start_shell() | ||
|
||
|
||
@kubectl.command(context_settings=CONTEXT_SETTINGS) | ||
@pass_container | ||
def configure(kctl: KubeCtlContainer): | ||
"""Automatically add the EKS cluster from the layer into your kubectl config file.""" | ||
kctl.configure() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
from click.exceptions import Exit | ||
|
||
|
||
def _handle_subcommand(context, cli_container, args, caller_name=None): | ||
"""Decide if command corresponds to a wrapped one or not and run accordingly. | ||
Args: | ||
context (click.context): Current context | ||
cli_container (LeverageContainer): Container where commands will be executed | ||
args (tuple(str)): Arguments received by Leverage | ||
caller_name (str, optional): Calling command. Defaults to None. | ||
Raises: | ||
Exit: Whenever container execution returns a non-zero exit code | ||
""" | ||
caller_pos = args.index(caller_name) if caller_name is not None else 0 | ||
|
||
# Find if one of the wrapped subcommand was invoked | ||
wrapped_subcommands = context.command.commands.keys() | ||
subcommand = next((arg for arg in args[caller_pos:] if arg in wrapped_subcommands), None) | ||
|
||
if subcommand is None: | ||
# Pass command to the container directly | ||
exit_code = cli_container.start(" ".join(args)) | ||
if not exit_code: | ||
raise Exit(exit_code) | ||
|
||
else: | ||
# Invoke wrapped command | ||
subcommand = context.command.commands.get(subcommand) | ||
if not subcommand.params: | ||
context.invoke(subcommand) | ||
else: | ||
context.forward(subcommand) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.