Skip to content

Commit

Permalink
feat: add support for downloading the correct version of kubeseal bin…
Browse files Browse the repository at this point in the history
…ary (#12)
  • Loading branch information
shini4i authored Jul 5, 2022
1 parent 19fe4d6 commit 8cbea2e
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 18 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.4.0] - 2022-07-05
### Added
- kubeseal-auto now downloads the same version of kubeseal binary as the version of sealed-secret controller (only in non-detached mode)

## [0.3.1] - 2022-07-04
### Fixed
- TypeError: argument of type 'NoneType' is not iterable in _find_sealed_secrets_controller
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pipx install kubeseal-auto
```

## Usage
By default, the script will check the version of sealed-secret controller and download the corresponding kubeseal binary to ~/bin directory.

To run the script in fully interactive mode:
```bash
Expand All @@ -24,6 +25,7 @@ kubeseal-auto --fetch
# Generate SealedSecret with local certificate
kubeseal-auto --cert <kubectl-context>-kubeseal-cert.crt
```
> Note: In the detached mode kubeseal-auto will not download the kubeseal binary and will look for in $PATH.
To select kubeconfig context:
```bash
Expand Down
18 changes: 9 additions & 9 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "kubeseal-auto"
version = "0.3.1"
version = "0.4.0"
description = "An interactive wrapper for kubeseal binary"
authors = ["Vadim Gedz <[email protected]>"]
license = "MIT"
Expand All @@ -17,6 +17,7 @@ questionary = "^1.10.0"
icecream = "^2.1.2"
PyYAML = "^6.0"
colorama = "^0.4.4"
requests = "^2.28.1"

[tool.poetry.dev-dependencies]

Expand Down
18 changes: 14 additions & 4 deletions src/kubeseal_auto/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
from icecream import ic
from kubernetes import client, config

from kubeseal_auto.host import Host


class Cluster:
def __init__(self, select_context: bool):
self.context = self._set_context(select_context=select_context)
config.load_kube_config(context=self.context)
self.host = Host()
self.controller = self._find_sealed_secrets_controller()

@staticmethod
Expand All @@ -30,8 +33,7 @@ def get_all_namespaces() -> list:
ic(ns_list)
return ns_list

@staticmethod
def _find_sealed_secrets_controller() -> dict:
def _find_sealed_secrets_controller(self) -> dict:
click.echo("===> Searching for SealedSecrets controller")

expected_label = "app.kubernetes.io/instance"
Expand All @@ -44,10 +46,15 @@ def _find_sealed_secrets_controller() -> dict:
if deployment.metadata.labels[expected_label] == "sealed-secrets":
name = deployment.metadata.labels[expected_label]
namespace = deployment.metadata.namespace
version = deployment.metadata.labels["app.kubernetes.io/version"]
click.echo(
f"===> Found the following controller: {Fore.CYAN}{namespace}/{name}"
f"===> Found the following controller: {Fore.CYAN}{namespace}/{name}:{version}"
)
return {"name": name, "namespace": namespace}
self.host.ensure_kubeseal_binary(version=version)
return {"name": name, "namespace": namespace, "version": version}

click.echo("===> No controller found")
exit(1)

click.echo("===> No controller found")
exit(1)
Expand Down Expand Up @@ -82,5 +89,8 @@ def get_controller_name(self):
def get_controller_namespace(self):
return self.controller["namespace"]

def get_controller_version(self):
return self.controller["version"].split("v")[-1]

def get_context(self):
return self.context
65 changes: 65 additions & 0 deletions src/kubeseal_auto/host.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import os
import platform

import click
import requests
from icecream import ic


class Host:
def __init__(self):
self.base_url = (
"https://github.com/bitnami-labs/sealed-secrets/releases/download"
)
self.bin_location = f"{os.path.expanduser('~')}/bin"
self.cpu_type = self._get_cpu_type()
self.system = self._get_system_type()

@staticmethod
def _get_cpu_type():
return platform.machine()

@staticmethod
def _get_system_type():
if platform.system() == "Darwin":
return "darwin"
elif platform.system() == "Linux":
return "linux"
else:
return "unsupported"

def _download_kubeseal_binary(self, version: str):
click.echo("Downloading kubeseal binary")

url = f"{self.base_url}/v{version}/kubeseal-{version}-{self.system}-{self.cpu_type}.tar.gz"
ic(url)

if not os.path.exists(self.bin_location):
os.makedirs(self.bin_location)

click.echo(f"Downloading {url}")
with requests.get(
f"{self.base_url}/v{version}/kubeseal-{version}-{self.system}-{self.cpu_type}.tar.gz"
) as r:
with open(
f"/tmp/kubeseal-{version}-{self.system}-{self.cpu_type}.tar.gz", "wb"
) as f:
f.write(r.content)

os.system(
f"tar -xvf "
f"/tmp/kubeseal-{version}-{self.system}-{self.cpu_type}.tar.gz "
f"-C {self.bin_location} kubeseal"
)
os.rename(
f"{self.bin_location}/kubeseal", f"{self.bin_location}/kubeseal-{version}"
)
os.remove(f"/tmp/kubeseal-{version}-{self.system}-{self.cpu_type}.tar.gz")

def ensure_kubeseal_binary(self, version: str):
version = version.split("v")[-1]
if not os.path.exists(f"{self.bin_location}/kubeseal-{version}"):
click.echo(
f"kubeseal binary not found at {self.bin_location}/kubeseal-{version}"
)
self._download_kubeseal_binary(version)
14 changes: 10 additions & 4 deletions src/kubeseal_auto/kubeseal.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,22 @@ class Kubeseal:
def __init__(self, select_context: bool, certificate=None):
self.detached_mode = False

self.binary = "kubeseal"

if certificate is not None:
click.echo("===> Working in a detached mode")
self.detached_mode = True
self.certificate = certificate
else:
home_dir = os.path.expanduser("~")
self.cluster = Cluster(select_context=select_context)
self.controller_name = self.cluster.get_controller_name()
self.controller_namespace = self.cluster.get_controller_namespace()
self.current_context_name = self.cluster.get_context()
self.namespaces_list = self.cluster.get_all_namespaces()
self.binary = (
f"{home_dir}/bin/kubeseal-{self.cluster.get_controller_version()}"
)

self.temp_file = NamedTemporaryFile()

Expand Down Expand Up @@ -128,13 +134,13 @@ def seal(self, secret_name: str):
click.echo("===> Sealing generated secret file")
if self.detached_mode:
command = (
f"kubeseal --format=yaml "
f"{self.binary} --format=yaml "
f"--cert={self.certificate} < {self.temp_file.name} "
f"> {secret_name}.yaml"
)
else:
command = (
f"kubeseal --format=yaml "
f"{self.binary} --format=yaml "
f"--context={self.current_context_name} "
f"--controller-namespace={self.controller_namespace} "
f"--controller-name={self.controller_name} < {self.temp_file.name} "
Expand All @@ -160,12 +166,12 @@ def merge(self, secret_name: str):
click.echo(f"===> Updating {secret_name}")
if self.detached_mode:
command = (
f"kubeseal --format=yaml --merge-into {secret_name} "
f"{self.binary} --format=yaml --merge-into {secret_name} "
f"--cert={self.certificate} < {self.temp_file.name} "
)
else:
command = (
f"kubeseal --format=yaml --merge-into {secret_name} "
f"{self.binary} --format=yaml --merge-into {secret_name} "
f"--context={self.current_context_name} "
f"--controller-namespace={self.controller_namespace} "
f"--controller-name={self.controller_name} < {self.temp_file.name}"
Expand Down

0 comments on commit 8cbea2e

Please sign in to comment.