Skip to content

Commit

Permalink
feat: import configmap from local directory at deploy time (#364)
Browse files Browse the repository at this point in the history
* feat: import configmap from local directory at deploy time

---------

Co-authored-by: Brandon Squizzato <bsquizza@redhat.com>
  • Loading branch information
akoserwal and bsquizz authored May 15, 2024
1 parent d0e7bb2 commit 795b524
Showing 5 changed files with 145 additions and 19 deletions.
53 changes: 47 additions & 6 deletions bonfire/bonfire.py
Original file line number Diff line number Diff line change
@@ -42,6 +42,7 @@
from bonfire.processor import TemplateProcessor, process_clowd_env, process_iqe_cji
from bonfire.qontract import get_apps_for_env, sub_refs
from bonfire.secrets import import_secrets_from_dir
from bonfire.configmaps import import_configmaps_from_dir
from bonfire.utils import (
FatalError,
check_pypi,
@@ -905,11 +906,11 @@ def _cmd_namespace_wait_on_resources(namespace, timeout, db_only):
@namespace.command("describe")
@click.argument("namespace", required=False, type=str)
@click.option(
"--output",
"-o",
default="cli",
help="which output format to return the data in",
type=click.Choice(["cli", "json"], case_sensitive=False),
"--output",
"-o",
default="cli",
help="which output format to return the data in",
type=click.Choice(["cli", "json"], case_sensitive=False),
)
def _describe_namespace(namespace, output):
"""Get current namespace info"""
@@ -1252,12 +1253,26 @@ def _check_and_reserve_namespace(name, requester, duration, pool, timeout, local
help="Import secrets from local directory at deploy time",
default=False,
)
@click.option(
"--import-configmaps",
is_flag=True,
help="Import configmaps from local directory at deploy time",
default=False,
)
@click.option(
"--secrets-dir",
type=str,
help="Directory to use for secrets import (default: " "$XDG_CONFIG_HOME/bonfire/secrets/)",
help="Directory to use for secrets import (default: $XDG_CONFIG_HOME/bonfire/secrets/)",
default=conf.DEFAULT_SECRETS_DIR,
)
@click.option(
"--configmaps-dir",
type=str,
help=(
"Directory to use for configmaps import (default: $XDG_CONFIG_HOME/bonfire/configmaps/)"
),
default=conf.DEFAULT_CONFIGMAPS_DIR,
)
@click.option(
"--no-release-on-fail",
is_flag=True,
@@ -1293,7 +1308,9 @@ def _cmd_config_deploy(
no_release_on_fail,
component_filter,
import_secrets,
import_configmaps,
secrets_dir,
configmaps_dir,
local,
frontends,
pool,
@@ -1326,6 +1343,9 @@ def _cmd_config_deploy(
if import_secrets:
import_secrets_from_dir(secrets_dir)

if import_configmaps:
import_configmaps_from_dir(configmaps_dir)

clowd_env = _get_env_name(ns, clowd_env)

def _err_handler(err):
@@ -1422,12 +1442,28 @@ def _cmd_process_clowdenv(namespace, quay_user, clowd_env, template_file, local)
help="Import secrets from local directory at deploy time",
default=False,
)
@click.option(
"--import-configmaps",
is_flag=True,
help="Import configmaps from local directory at deploy time",
default=False,
)
@click.option(
"--secrets-dir",
type=str,
help=("Import secrets from this directory (default: " "$XDG_CONFIG_HOME/bonfire/secrets/)"),
default=conf.DEFAULT_SECRETS_DIR,
)
@click.option(
"--configmaps-dir",
type=str,
help=(
"Import configmaps from this directory \
(default: "
"$XDG_CONFIG_HOME/bonfire/configmaps/)"
),
default=conf.DEFAULT_CONFIGMAPS_DIR,
)
@options(_ns_reserve_options)
@options(_timeout_option)
@click_exception_wrapper("deploy-env")
@@ -1438,7 +1474,9 @@ def _cmd_deploy_clowdenv(
template_file,
timeout,
import_secrets,
import_configmaps,
secrets_dir,
configmaps_dir,
name,
requester,
duration,
@@ -1455,6 +1493,9 @@ def _cmd_deploy_clowdenv(
if import_secrets:
import_secrets_from_dir(secrets_dir)

if import_configmaps:
import_configmaps_from_dir(configmaps_dir)

clowd_env_config = _process_clowdenv(namespace, quay_user, clowd_env, template_file, local)

log.debug("ClowdEnvironment config:\n%s", clowd_env_config)
5 changes: 3 additions & 2 deletions bonfire/config.py
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@
DEFAULT_CONFIG_PATH = get_config_path().joinpath("config.yaml")
DEFAULT_ENV_PATH = get_config_path().joinpath("env")
DEFAULT_SECRETS_DIR = get_config_path().joinpath("secrets")
DEFAULT_CONFIGMAPS_DIR = get_config_path().joinpath("configmaps")

DEFAULT_NAMESPACE_POOL = "default"

@@ -84,7 +85,7 @@
ELASTICSEARCH_INDEX = os.getenv("ELASTICSEARCH_INDEX", DEFAULT_ELASTICSEARCH_INDEX)
ELASTICSEARCH_APIKEY = os.getenv("ELASTICSEARCH_APIKEY")
if ELASTICSEARCH_APIKEY:
ELASTICSEARCH_APIKEY = f'ApiKey {ELASTICSEARCH_APIKEY}'
ELASTICSEARCH_APIKEY = f"ApiKey {ELASTICSEARCH_APIKEY}"
ENABLE_TELEMETRY = os.getenv("ENABLE_TELEMETRY", DEFAULT_ENABLE_TELEMETRY).lower() == "true"

CLIENT_ID = os.getenv("CLIENT_ID", DEFAULT_CLIENT_ID)
@@ -100,7 +101,7 @@
"host-inventory",
"host-inventory-frontend",
"unleash-proxy",
"service-accounts"
"service-accounts",
)


83 changes: 83 additions & 0 deletions bonfire/configmaps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""
Handles importing of configmaps from a local directory
"""
import glob
import json
import logging
import os

from ocviapy import get_json, oc

from bonfire.utils import FatalError, load_file

log = logging.getLogger(__name__)


def _parse_configmaps_file(path):
"""
Return a dict of all configmaps in a file with key: configmap name,
val: parsed configmap json/yaml
The file can contain 1 configmap, or a list of configmaps
"""
content = load_file(path)
configmaps = {}
if content.get("kind").lower() == "list":
items = content.get("items", [])
else:
items = [content]

for item in items:
if item.get("kind").lower() == "configmap":
try:
configmaps[item["metadata"]["name"]] = item
except KeyError:
raise FatalError("Configmap at path '{}' has no metadata/name".format(path))

return configmaps


def _get_files_in_dir(path):
"""
Get a list of all .yml/.yaml/.json files in a dir
"""
files = list(glob.glob(os.path.join(path, "*.yaml")))
files.extend(list(glob.glob(os.path.join(path, "*.yml"))))
files.extend(list(glob.glob(os.path.join(path, "*.json"))))
return files


def _import_configmaps(configmap_name, configmap_data):
# get existing configmap in the ns (if it exists)
current_configmap = get_json("configmap", configmap_name) or {}

# avoid race conditions when running multiple processes by comparing the data
data_mismatch = current_configmap.get("data") != configmap_data.get("data")
if data_mismatch:
log.info("replacing configmap '%s' using local copy", configmap_name)
# delete from dst ns so that applying 'null' values will work
oc("delete", "--ignore-not-found", "configmap", configmap_name, _silent=True)
oc("apply", "-f", "-", _silent=True, _in=json.dumps(configmap_data))


def import_configmaps_from_dir(path):
if not os.path.exists(path):
raise FatalError(f"configmaps directory not found: {path}")

if not os.path.isdir(path):
raise FatalError(f"invalid configmaps directory: {path}")

files = _get_files_in_dir(path)
configmaps = {}
log.info("importing configmaps from local path: %s", path)
for confmaps_file in files:
confmaps_in_file = _parse_configmaps_file(confmaps_file)
log.info("loaded %d configmap(s) from file '%s'", len(confmaps_in_file), confmaps_file)
for confmaps_name in confmaps_in_file:
if confmaps_name in configmaps:
raise FatalError(
f"configmap with name '{confmaps_name}' defined twice in configmaps dir"
)
configmaps.update(confmaps_in_file)

for configmap_name, configmap_data in configmaps.items():
_import_configmaps(configmap_name, configmap_data)
3 changes: 2 additions & 1 deletion bonfire/elastic_logging.py
Original file line number Diff line number Diff line change
@@ -22,7 +22,8 @@ def __init__(self):
)
if not self.es_handler:
self.es_handler = AsyncElasticsearchHandler(
f'{conf.ELASTICSEARCH_HOST}/{conf.ELASTICSEARCH_INDEX}/_doc/')
f"{conf.ELASTICSEARCH_HOST}/{conf.ELASTICSEARCH_INDEX}/_doc/"
)
self.es_telemetry.addHandler(self.es_handler)

def send_telemetry(self, log_message, success=True):
20 changes: 10 additions & 10 deletions bonfire/namespaces.py
Original file line number Diff line number Diff line change
@@ -401,16 +401,16 @@ def describe_namespace(project_name: str, output: str):
data += f"Default user login: {kc_creds['defaultUsername']} | {kc_creds['defaultPassword']}\n"
if output == "json":
data = {
"namespace": project_name,
"keycloak_admin_route": keycloak_url,
"keycloak_admin_username": kc_creds['username'],
"keycloak_admin_password": kc_creds['password'],
"clowdapps_deployed": num_clowdapps,
"frontends_deployed": num_frontends,
"default_username": kc_creds['defaultUsername'],
"default_password": kc_creds['defaultPassword'],
"gateway_route": f"https://{fe_host}",
}
"namespace": project_name,
"keycloak_admin_route": keycloak_url,
"keycloak_admin_username": kc_creds["username"],
"keycloak_admin_password": kc_creds["password"],
"clowdapps_deployed": num_clowdapps,
"frontends_deployed": num_frontends,
"default_username": kc_creds["defaultUsername"],
"default_password": kc_creds["defaultPassword"],
"gateway_route": f"https://{fe_host}",
}
data = json.dumps(data, indent=2)

return data

0 comments on commit 795b524

Please sign in to comment.