• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

binbashar / leverage / 11988849192

23 Nov 2024 04:54PM UTC coverage: 60.428% (+0.4%) from 60.037%
11988849192

Pull #281

github

Franr
testing discover happy path
Pull Request #281: [BV-599] Kubeconfig helper

194 of 458 branches covered (42.36%)

Branch coverage included in aggregate %.

52 of 72 new or added lines in 4 files covered. (72.22%)

46 existing lines in 5 files now uncovered.

2518 of 4030 relevant lines covered (62.48%)

0.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.77
/leverage/containers/kubectl.py
1
import os
1✔
2
from dataclasses import dataclass
1✔
3
from pathlib import Path
1✔
4

5
from click.exceptions import Exit
1✔
6
from docker.types import Mount
1✔
7
import ruamel.yaml
1✔
8
import simple_term_menu
1✔
9

10
from leverage import logger
1✔
11
from leverage._utils import AwsCredsEntryPoint, ExitError, CustomEntryPoint
1✔
12
from leverage.container import TerraformContainer
1✔
13

14

15
@dataclass
1✔
16
class ClusterInfo:
1✔
17
    cluster_name: str
1✔
18
    profile: str
1✔
19
    region: str
1✔
20

21

22
class KubeCtlContainer(TerraformContainer):
1✔
23
    """Container specifically tailored to run kubectl commands."""
24

25
    KUBECTL_CLI_BINARY = "/usr/local/bin/kubectl"
1✔
26
    KUBECTL_CONFIG_PATH = Path(f"/home/{TerraformContainer.CONTAINER_USER}/.kube")
1✔
27
    KUBECTL_CONFIG_FILE = KUBECTL_CONFIG_PATH / Path("config")
1✔
28
    METADATA_FILENAME = "metadata.yaml"
1✔
29

30
    def __init__(self, client):
1✔
31
        super().__init__(client)
1✔
32

33
        self.entrypoint = self.KUBECTL_CLI_BINARY
1✔
34

35
        self.host_kubectl_config_dir = Path.home() / Path(f".kube/{self.project}")
1✔
36
        if not self.host_kubectl_config_dir.exists():
1✔
37
            # make sure the folder exists before mounting it
38
            self.host_kubectl_config_dir.mkdir(parents=True)
1✔
39

40
        self.container_config["host_config"]["Mounts"].append(
1✔
41
            # the container is expecting a file named "config" here
42
            Mount(
43
                source=str(self.host_kubectl_config_dir),
44
                target=str(self.KUBECTL_CONFIG_PATH),
45
                type="bind",
46
            )
47
        )
48

49
    def start_shell(self):
1✔
50
        with AwsCredsEntryPoint(self, override_entrypoint=""):
1✔
51
            self._start(self.SHELL)
1✔
52

53
    def configure(self, ci: ClusterInfo = None):
1✔
54
        """
55
        Add the given EKS cluster configuration to the .kube/ files.
56
        """
57
        if ci:
1✔
58
            # if you have the details, generate the command right away
NEW
59
            cmd = f"aws eks update-kubeconfig --region {ci.region} --name {ci.cluster_name} --profile {ci.profile}"
×
60
        else:
61
            # otherwise go get them from the layer
62
            logger.info("Retrieving k8s cluster information...")
1✔
63
            with CustomEntryPoint(self, entrypoint=""):
1✔
64
                cmd = self._get_eks_kube_config()
1✔
65

66
        logger.info("Configuring context...")
1✔
67
        with AwsCredsEntryPoint(self, override_entrypoint=""):
1✔
68
            exit_code = self._start(cmd)
1✔
69

70
        if exit_code:
1✔
71
            raise Exit(exit_code)
×
72

73
        logger.info("Done.")
1✔
74

75
    def _get_eks_kube_config(self) -> str:
1✔
76
        exit_code, output = self._start_with_output(f"{self.TF_BINARY} output -no-color")  # TODO: override on CM?
1✔
77
        if exit_code:
1✔
78
            raise ExitError(exit_code, output)
1✔
79

80
        aws_eks_cmd = next(op for op in output.split("\r\n") if op.startswith("aws eks update-kubeconfig"))
1✔
81
        return aws_eks_cmd + f" --region {self.region}"
1✔
82

83
    def _scan_clusters(self):
1✔
84
        """
85
        Scan all the subdirectories in search of "cluster" metadata files.
86
        """
87
        for root, dirs, files in os.walk(self.paths.cwd):
1✔
88
            # exclude hidden directories
89
            dirs[:] = [d for d in dirs if not d[0] == "."]
1✔
90

91
            for file in files:
1✔
92
                if file != self.METADATA_FILENAME:
1✔
93
                    continue
1✔
94

95
                cluster_file = Path(root) / file
1✔
96
                try:
1✔
97
                    with open(cluster_file) as cluster_yaml_file:
1✔
98
                        data = ruamel.yaml.safe_load(cluster_yaml_file)
1✔
99
                    assert data["type"] == "k8s-eks-cluster"
1✔
NEW
100
                except AssertionError:
×
NEW
101
                    continue
×
NEW
102
                except Exception as exc:
×
NEW
103
                    logger.warning(exc)
×
NEW
104
                    continue
×
105
                else:
106
                    yield Path(root), data
1✔
107

108
    def discover(self):
1✔
109
        """
110
        Do a scan down the tree of subdirectories looking for k8s clusters metadata files.
111
        Open up a menu with all the found items, where you can pick up and configure it on your .kubeconfig file.
112
        """
113
        cluster_files = [(path, data) for path, data in self._scan_clusters()]
1✔
114
        if not cluster_files:
1✔
NEW
115
            raise ExitError(1, "No clusters found.")
×
116

117
        terminal_menu = simple_term_menu.TerminalMenu(
1✔
118
            [f"{c[1]['data']['cluster_name']}: {str(c[0])}" for c in cluster_files], title="Clusters found:"
119
        )
120
        menu_entry_index = terminal_menu.show()
1✔
121
        if menu_entry_index is None:
1✔
122
            # selection cancelled
NEW
123
            return
×
124

125
        layer_path = cluster_files[menu_entry_index][0]
1✔
126
        cluster_data = cluster_files[menu_entry_index][1]
1✔
127
        cluster_info = ClusterInfo(
1✔
128
            cluster_name=cluster_data["data"]["cluster_name"],
129
            profile=cluster_data["data"]["profile"],
130
            region=cluster_data["data"]["region"],
131
        )
132

133
        # cluster is the host path, so in order to be able to run commands in that layer
134
        # we need to convert it into a relative inside the container
135
        self.container_config["working_dir"] = (
1✔
136
            self.paths.guest_base_path / layer_path.relative_to(self.paths.cwd)
137
        ).as_posix()
138
        # now simulate we are standing on the chosen layer folder
139
        self.paths.update_cwd(layer_path)
1✔
140
        self.configure(cluster_info)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc